关于php的各种比较
md5碰撞
当php对于利用"!="或者"=="对两个哈希字符串进行比较时,它会把每一个以"0E"开头的哈希值解释为0,其实就0*10的n次方,永远解释为0,所以产生了最为经典md5()漏洞。
例如 s878926199a
与s155964671a
他们的哈希值都是以0E开头的,分别为0e545993274517709034328855841020
和0e342768416822451524974117254469
当采用"=="比较时,会返回true。
例如下列例子。
<?php
if(md5("s878926119a")==md5("s155964671a")){
echo "php is so difficult";
}
else{
echo "php is so strong";
}
?>

在阅读php and MySQL 开发一书中的时候,我发现了一个问题,md5()在处理参数的时候,是不能够处理字符串的。
所以在进行比较的时候,传入数组也是能够触发的。
为了观察方便,我写下了如下代码:
<?php
$a = md5($_GET['a']);
var_dump($a);
$b = NULL;
$b = md5($b);
echo "$b";
$c = array("NULL");
$c = md5($c);
var_dump($c);
?>

可以清楚的看见,md5在处理数组时,是返回的NULL;也就是返回的0;也就是说,若是md5采用“==”比较两值或者“==“0时,只要变量可控,是可以绕过的。
在php中还有一些特性。
- 当定义一个变量,如果没有设置值,默认为0。
- php的一个错误抑制符@,可以将表达式产生的任何错误忽略掉。
- 例如上述代码,php在采用$_GET方法赋值时,如果$a传入[]=1这样的数组时,php会把这个当做数组传入,然后自动对参数调用 urldecode。$_POST也是同样如此。
总结,在进行md5==0有如下几种方法
- 0或0e开头的md5值
- 传入数组这种类型的还有一些php的内置函数,比如strcmp strpos sha1 ereg(低版本php:php4,php5)
- 注:在高版本php里,ereg被preg取代,且preg可以操作数组。

通过传入数组的方式,绕过 strcmp
<?php
if(isset($_GET['a'])){
if(strcmp($_GET['a'],$_GET['b'])==0){
echo "php is so difficult";
}
}
else{
echo "php is so strong"
}
?>

strcmp函数抛出异常,并返回0值。
数字比较
在传入一个变量值为整形时,该整形与一个其他类型进行==(等于运算符)比较的时候,会先把其他类型intval再进行比较。
例如将"123a"与123比较,会返回true。
<?php
if(isset($_GET['a'])){
if(!is_numeric($_GET['a'])){
if($_GET['a']==123){
echo "php is so difficult";
}
}
}
else{
echo "php is so strong";
}
?>

但是如果采用===(恒等运算符)比较的话。

会返回false。
switch比较
<?php
$flag = "flag{k0dosan_4s_s0_hands0me}";
if (isset($_GET['a'])){
$a = md5($_GET['a']);
switch($a){
case 0:
case 1:
case 2:
case 3:
case 4:
echo $flag;
break;
default:
echo "木大木大木大木大木大";
}
})
?>
在这个程序里,首先传入a,然后再switch里对a进行判断。正常思维情况下,应该是如果a不等于1234当中的一个时,会在switch里挨个比较,不等于0进入1,以此类推。若a=3则进入3,若都失败进入default。
但是php里,如果传入了0的话没有写入break的话,这个时候是默认比较成功,下面的case不会再进行比较。直接进入case4输出flag。
若是在比赛中遇到(虽然不太可能)对a加入非数字的限制时,就可以传入数组等绕过。

且如果 switch 是数字类型的 case 的判断时, switch 会将其中的参数转换为 int类型。

将md5删去。

可以绕过 !is_numeric
php文件包含复现
复现前的环境准备:
PHPstudy;
以及
<?php
echo "come on! just fuking";
if (isset($_REQUEST['file'])){
include($_REQUEST['file']);
}
?>
这样简单的一个环境。为了复现的顺利进行,我将allow_url_fopen和allow_url_include都打开。
没有任何过滤,我们只需要控制文件内容就可以。
首先是各种各样的伪协议。
file:// — 访问本地文件系统 http:// — 访问 HTTP(s) 网址 ftp:// — 访问 FTP(s) URLs php:// — 访问各个输入/输出流(I/O streams) zlib:// — 压缩流 data:// — 数据(RFC 2397) glob:// — 查找匹配的文件路径模式 phar:// — PHP 归档 ssh2:// — Secure Shell 2 rar:// — RAR压缩流 ogg:// — 音频流 expect:// — 处理交互式的流
比赛常用伪协议
file://
该伪协议用于访问本地文件系统,参数值要绝对路径
且不受allow_url_fopen allow_url_include限制

php://
- php://用于访问各个输入输出流在比赛中常常有两种用法
- php://input是个可以访问请求的原始数据的只读流,将读入数据当做php代码读取。根据官方说法,可以读取从来没有处理过的POST数据,但不能用于enctype=multipart/form-data”当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。且allow_url_include=On。值得注意的是:
- 只有Coentent-Type不为multipart/form-data的时候,PHP不会将http请求数据包中的相应数据填入php://input,否则其它情况都会。填入的长度,由Coentent-Length指定。
- 只有Content-Type为application/x-www-data-urlencoded时,php://input数据才跟$_POST数据相一致。
- php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用,可以用一个表格展示。
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 该参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔 |
write=<写链的筛选列表> | 同上 |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链 |
个人认为,在比赛中php://input常常用于执行php代码,而php://filter用于通过base64编码的方式读取文件源码。
php://input由于我的hackbar出现了一点问题,所以这里我没办法给出截图(嘤嘤嘤)。

这里给出一个涙笑师傅给的filter的各种过滤器的网址:https://www.cnblogs.com/natian-ws/p/7242477.html
zip://
该伪协议的应用条件是php版本大于等于5.3且包含文件要使用绝对路径
将木马弄成压缩包 1.php?filename=zip://D:\1.zip#2.txt
phar://
与zip相似,但可以使用相对路径,前提是包在当前目录下
php版本大于等于5.3 1
利用姿势:
1.php?filename=phar://D:/1.zip/2.txt
这两种的解法在下面。
data://
该协议要求php版本大于等于5.2 1,且allow_url_fopen=On与allow_url_include=On
有几种利用姿势
- 1.php?filename=data://text/plain,
<?php phpinfo(); ?>
- 执行命令:1.php?filename=data://text/plain,
<?php system('ls'); ?>
- 通过base64编码1.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

webdav远程包含
其实并不一定需要webdav服务,只要是能够访问那个装有shell的文件,都可以搞。
由于这里我在阿里云机子上开启webdav服务失败了,所以我在我的另一台电脑上,开启了webdav服务。并进行了包含。

包含日志文件
很多时候,web服务器会请求写入到日志文件中,在用户发送请求时,会将请求写入access/log,当发生错误时将错误写入error.log。通常情况下,日志保存路径在/var/log/apache2。
windows采用phpstudy集成的环境下,一般在E:/phpstudy_pro/WWW/Extensions/Apache/log下
这里我通过直接在浏览器访问的方式访问日志文件

查询日志文件

然后文件包含

存在图片上传文件等功能
任意情况下文件包含
在shell.txt里写入小马然后改文件后缀成任意文件,比如png即可实现文件包含

限制后缀的文件
<?php
echo "come on! just fuking";
$file=$_GET['file'].".php"; //限制只能上传.php后缀的文件
if(isset($file)){
include($file);
}
?>
利用zip://
或者phar://
伪协议.将shell打成一个包。
zip://
需要用绝对路径zip://有好几种用法。最常用的就是采用#读取。为了防止浏览器的编码我们可以用%23.

phar://

LFI+phpinfo()
php在处理我们上传的表单时,也会生成一个对应的临时文件。这个临时文件我们可以在$_File查看到
于是我们可以通过构造这样一个表单,仿造官方的表单进行提交,查询我们的$_FILE临时文件名
<html>
<body>
<form action="http://localhost/WWW/test4.php" method="POST" enctype="multipart/form-data">
<input type="file" name="file1" />
<input type="submit" />
</form>
</body>
我们随意提交一个含有shell的文件上去。

这个文件在处理完表单就会删除,于是我们就要用到条件竞争。
PS:后面是我在靶机上完成的
运用了GitHub上的开源项目vulhub的exp.py成功getshell。
https://github.com/vulhub/vulhub


包含session
关于session:session内容一般以文件的形式存储于服务器中,而本地浏览器会存储一个与服务器中session文件对应的Cookie值,Cookie存储的是键值为“PHPSESSID”的Seeion_id值,用户在访问web应用时,每次跳转发生http请求时,会自动把这个存储session_id的Cookie值发送过去,因此web应用的所有页面都可以获取到这个SESSION_ID值,也就可以通过session_id获取服务器中存储的session值,当用户关闭浏览器后,cookie存储的session_id自动清除,一般服务器存储的session文件也会在30分钟后自动清除。
我们要包含session,就必须要知道session的位置。
构造如下的上传表单,仿造的别人的师傅的
<html>
<body>
<form action="http://localhost:8080/test3.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();?>" />
<input type="file" name="file">
<input type="submit" />
</form>
</body>
得到了phpinfo页面,同时在该页面知道我们的session存储位置

同时得知了创造了一个名字为什么样的session文件。

下面进行条件竞争达到getshell的目的,这里用其他师傅的脚本,自己改了改。

可以看到成功写入

php崩溃
不知道是因为集成环境的原因还是什么,我始终无法复现成功。原理就是,通过构造http://yourip/test.php?file=php://filter/string.strip_tags=/etc/passwd
包含一个存在的文件造成php7的内存爆出,在这个时候如果我们post一个包上去,上文可知,这时会创建一个临时文件,但在这个情况下这个临时文件并不会被删去,然后进行包含 .
php还有许许多多的漏洞,我会随着我的学习继续更新到博客上。
Comments | NOTHING