PHP代码审计系列(五)
本系列将收集多个PHP代码安全审计项目从易到难,并加入个人详细的源码解读。此系列将进行持续更新。
数字验证正则绕过
源码如下
<?php
error_reporting(0);
$flag = 'flag{test}';
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配
{
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'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
//>=3,必须包含四种类型三种与三种以上
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
?>
通读代码逻辑如下
首先有一个判断,若POST传入的password字符串长度小于12或字符串中存在空格或TAB则输出Wrong Format退出脚本
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配
{
echo 'Wrong Format';
exit;
}
接下来进入死循环
匹配规则为标点符号、数字、大小写字母
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
若判断匹配规则超过6次直接退出循环
if (6 > preg_match_all($reg, $password, $arr))
遍历数组,若password正则匹配(包含三种或三种以上的类型)则变量c增加1
array('punct', 'digit', 'upper', 'lower')
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
若c小于3直接退出循环
if ($c < 3) break;
最后如果判断42==password则输出flag
if ("42" == $password) echo $flag;
绕过payload
password=42.0e+000000
成功输出flag
弱类型整数大小比较绕过
源码如下
<?php
error_reporting(0);
$flag = "flag{test}";
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336){
echo $flag;
}
?>
通读代码,会将password进行数字判断如果为数字则终止脚本
is_numeric($temp)?die("no numeric"):NULL;
并且必须大于1336
if($temp>1336){
echo $flag;
}
利用弱类型绕过,payload:1337a,在比较时会is_numeric会判断为字符串成功绕过。由于PHP弱类型一个整形和一个其他类型比较时1337a会被转为1337再进行比较
md5函数验证绕过
源码如下
<?php
error_reporting(0);
$flag = 'flag{test}';
$temp = $_GET['password'];
if(md5($temp)==0){
echo $flag;
}
?>
主要绕过的是这句话
if(md5($temp)==0)
很多都可以绕过
xxx.php?password
xxx.php
xxx.php?password=1
...
md5函数true绕过注入
源码如下
<?php
error_reporting(0);
$link = mysql_connect('localhost', 'root', 'root');
if (!$link) {
die('Could not connect to MySQL: ' . mysql_error());
}
// 选择数据库
$db = mysql_select_db("security", $link);
if(!$db)
{
echo 'select db error';
exit();
}
// 执行sql
$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
var_dump($sql);
$result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );
$row1 = mysql_fetch_row($result);
var_dump($row1);
mysql_close($link);
?>
主要绕过的就是以下sql
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
需要password在md5加密后实现sql注入
例如:
SELECT * FROM admin WHERE pass = ''or'xxx'
而ffifdyop字符串在md5后为
276f722736c95d99e921722cf9ed621c
该hex转换为字符串为
'or'6<trash>
最终payload=ffifdyop
switch没有break字符与0比较绕过
源码如下
<?php
error_reporting(0);
if (isset($_GET['which']))
{
$which = $_GET['which'];
switch ($which)
{
case 0:
case 1:
case 2:
require_once $which.'.php';
echo $flag;
break;
default:
echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
break;
}
}
?>
该题在比较which值时,当传入的是字符串(无法转换为数字)在与0比较时为true,而在判断为0时并没有break会直接向下执行到case2输出flag,而不是直接默认default。
payload为包含文件名:?which=xxx