前言
代码审计,是对应用程序源代码进行系统性检查的工作。 目的是为了找到并且修复应用程序在开发阶段存在的一些漏洞或者程序逻辑错误,避免程序漏洞被非法利用给企业带来不必要的风险.
基础
phpinfo
phpinfo(int $flags = INFO_ALL): bool
flags:
INFO_ALL -1 显示以上所有信息。(默认)
INFO_GENERAL 1 配置的命令行、php.ini 的文件位置、建立的时间、Web 服务器、系统及更多其他信息。
INFO_ENVIRONMENT 16 环境变量信息也可以用 $_ENV 获取。
INFO_VARIABLES 32 显示所有来自 EGPCS (Environment, GET, POST, Cookie, Server) 的 预定义变量。
phpinfo()函数返回的信息
1 | Core |
php预变量
常量
1 | PHP_VERSION PHP_VERSION |
变量
1 | $_SERVER['HTTP_HOST'] 主机地址 |
命名空间
定义命名空间
PHP 命名空间类似与文件系统,可分为相对命名空间和绝对命名空间
1 | //file1.php |
使用命名空间
use是使用命名空间,相当于java中的导包,前提是包中的文件需要提前require或者include进来
1 | //导入类 |
自动加载
1 |
|
php反射
在 PHP 中,反射(Reflection)是一种机制,用于在运行时动态地获取类、接口、函数、方法等的信息。反射机制允许我们在运行时分析和修改代码结构,包括类的属性和方法等。
1 | class MyClass |
回调
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数。能够降低系统的复杂度,提高代码的可维护性。
验证
is_callable 验证值是否可以在当前范围内作为函数调用。
PHP的url解析特性
在php中””包裹的字符串是可以被进一步解析的,例echo("$_GET[a]");
,而’’包裹的字符串则不能,PHP的url参数的值可视为包裹在””的字符串
- 查询字符串解析
值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)
。
[
会与\
匹配(匹配遵循就进原则)没有匹配上的[
不会被转义,即匹配成功就不会进行第二次替换,所以可以利用[
传入含有特殊字符的变量,如/?my[secret.flag=123
=my.secret.flag
2. $_SERVER['QUERY_STRING']
$_SERVER['QUERY_STRING']
提取?后的内容不会进行URL解码,而$_GET
和$_REQUEST
解析url时会先进行URL解码,可以进行URL编码绕过
$_REQUEST
$_REQUEST在同时接收GET和POST参数时,POST优先级更高,优先接受post参数。basename函数
basename() 函数返回路径中的文件名部分 basename()不能识别ascii值为`47、128-255`的字符 http://localhost/?file=%ffindex.php/%ff //index.php http://localhost/?file=%ffindex.php //index.php http://localhost/?file=index.php%ff //index.php <?php $path = "/testweb/home.php"; //显示带有文件扩展名的文件名 echo basename($path); //显示不带有文件扩展名的文件名 echo basename($path,".php"); ?> home.php home
php进阶
PDO
PDO (PHP Data Objects) 是PHP的一种数据库扩展,PDO使用的是面向对象的编程风格,php5之后自带PDO,它有以下‘好处’:
- 支持不同数据库
- 预处理语使用PDO预处理语句来执行数据库操作可以避免SQL注入的风险
PDO会对传入的每个参数进行转义和类型转换(如果传入的参数是字符串类型,但数据库需要的是整型,PDO会将字符串转换成整型,以确保数据类型的正确性)使用
PDO 预处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 发送预处理指令
$stmt=$pdo->prepare($pre_sql);//返回PDOStatement对象
// 捆绑值
$stmt->bindValue(':id',$id);
//绑定变量
$stmt->bindParam(':id',$id);//占位符的占位符(狗头)
//执行
$stmt->execute();
//# 查结果
//PDOStatement::fetch — 从结果集中获取下一行
$data=$stmt->fetchall(PDO::FETCH_ASSOC);
//PDO::FETCH_ASSOC:返回一个索引为结果集列名的数组
//PDO::FETCH_BOTH(默认):返回一个索引为结果集列名和以0开始的列号的数组绕过
md5绕过
md5的弱比较绕过
0e开头且e后为数字可以被解析成科学计数法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这里附上常见的0E开头的MD5
0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s532378020a
0e220463095855511507588041205815
s1665632922a
0e731198061491163073197128363787
s1184209335a
0e072485820392773389523109082030
s1885207154a
0e509367213418206700842008763514
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
0e215962017
0e291242476940776845150308577824md5的强比较绕过
强类型比较(===),判断内容的基础上,还会判断类型是否相同
但md5不能加密数组,传入数组会报错,但会继续执行并且返回结果为null,所以md5加密后的结果是下面这样null===null
,结果返回trueMD5碰撞
MD5碰撞也叫哈希碰撞,是指两个不同内容的输入,经过散列算法后,得到相同的输出,也就是两个不同的值的散列值相同1
2
3
4
5
6
7
8md5
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
!!!! !注意写题时抓包修改,不要使用HackBar插件
sha1
a=%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%B1md5($pass,true)的绕过
string 必需。要计算的字符串。
raw 默认不写为FALSE:32位16进制的字符串,TRUE:16位原始二进制格式的字符串1
2
3
4
5
6
7
8
9content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: 'or'6]!r,b
content: 129581926211651571912466741651878684928
hex: 06da5430449f8f6f23dfc1276f722738
raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
string: T0Do#'or'8在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数,那么返回值也是true。
字符串检测绕过
strcmp() 函数比较两个字符串(适用5.3版本以前的php)
1
2
3
4
5
6
7
8
9
10
11
12$str1 = "aixuexiwang.com";
$str2 = "aixuexiwang.com";
$str3 = "aixuexiwang";
echo strcmp($str1,$str2); //输出结果为0,因为,两个字符串相等。
echo strcmp($str1,$str3); //输出结果为4,因为,参数1比参数2,多了4个字符。
echo strcmp($str3,$str1); //输出结果为-4,因为,参数1比参数2少了4个字符。
$str4 = "abc";
$str5 = "ABC";
echo strcmp($str4,$str5); //输出结果为1,因为,区分大小写。
echo strcmp($str5,$str4); //输出结果为-1,因为,区分大小写。
echo strcasecmp($str4,$str5); //输出结果为0,因为,不区分大小写,所以,相等。strcmp()在比较字符串和数组的时候直接返回0,可用于绕过。
ereg()与strpos()两个函数同样不能用数组作为参数,否则返回NULL。
2. ereg()类似于preg_match但发现模式会返回true,否则返回false(PHP 5.3后已弃用)
eregi函数还可以使用%00来进行截断匹配
3. strpos() f函数查找字符串在另一字符串中第一次出现的位置(区分大小写),如果没有找到字符串则返回 FALSE(字符串位置从 0 开始,不是从 1 开始。)
相关函数:
strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
strripos() -查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
- preg_match绕过
preg_match(返回 pattern 的匹配结果,0 (不匹配)或 1或false)
preg_match_all(所有匹配 pattern 给定正则表达式的匹配结果并且将它们以 flag 指定顺序输出到$array数组中,返回值为完整匹配次数,输出的 $array 数组中,$array[0]保存完整模式的所有匹配, $array[1] 保存第一个子组的所有匹配)作用相当于python中的re.findall()
如果检查为if(preg_match(‘/…/‘, $json)),不管preg_match返回0还是false,if都不成立
数组绕过
preg_match只能处理字符串,当传入的subject是数组时会返回false在非多行模式(没有m修饰符)利用%0a绕过
非多行模式下,preg_match只能识别一行
如
1 | if (preg_match('/^.*(flag).*/', $json)) { |
- 利用PCRE回溯次数限制绕过(只能post)
膜拜大佬https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html?page=2#reply-list
DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit=1000000,超过10万次回溯就会返回false
一· 贪婪的.*
.*匹配剩余的所以字符,但正则后续要匹配,就会触发回溯
二. 懒惰的.+?
1 | if(preg_match('/UNION.+?SELECT/is', $input)) { |
- str_replace函数绕过
- intval() 函数用于获取变量的整数值
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。
最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647(2的31次方)。
举例:在这样的系统上, intval(‘1000000000000’) ,只取后31位会返回 2147483647。
64 位系统上,最大带符号的 integer 值是 9223372036854775807。
字符串有可能返回 0,虽然取决于字符串最左侧的字符。
1 | echo intval(0x1A); // 0x表示16进制,转化为十进制为26 |
利用
查看信息
get_defined_vars() 函数返回作用域内所有已定义的变量、环境变量、服务器变量、用户定义变量列表。
phpinfo() 输出关于php配置的信息
getenv() 获取一个环境变量的值
get_current_user() 获取当前php脚本所有者的名称
getlastmod() 获取页面最后修改的时间
ini_get() 获取一个配置选项的值
glob() 寻找与模式匹配的文件路径
为变量赋值
file_get_contents()函数将整个文件读入一个字符串中,并返回这个值,我们也可以利用
php://input
或data://text/plain,
自定义参数值extract()函数:从数组中将变量导入当前符号表。
定义:
从数组中将变量导入到当前的符号表
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量
语法:extract(array,extract_rules,prefix),array,必需,要使用的数组1
2
3
4
5
6<?php
$a="hello";
$b= array('a' =>"world" ,"b"=>"gogogo");
extract($b);
echo $a; //world
?>如上所示,会存在一个覆盖漏洞。
SSRF的危险函数
file_get_contents()
fsockopen()
curl_exec()
fopen()
readfile()