跳转至

Cross Site Request Forgery Challenge One(跨站请求伪造 (CSRF) 1)

题介绍

是一个CSRF相关的题,需要另一个用户配合才能完成

功能实现

点击提交消息 获得请求

POST /challenges/s74a796e84e25b854906d88f622170c1c06817e72b526b3d1e9a6085f429cf52 HTTP/1.1


myMessage=http%3A%2F%2Fshepherd-cn.anquanbiji.com%2Fuser%2Fcsrfchallengeone%2Fplusplus%3Fuserid%3Df6a93701ead755aa4fd140215b9ca449ce7f3476&csrfToken=75755217751292205141503195192721769803

对应实现代码在 src/main/java/servlets/module/challenge/CsrfChallengeOne.java 中

    String myMessage = request.getParameter("myMessage");
    log.debug("User Submitted - " + myMessage);
    myMessage = Validate.makeValidUrl(myMessage);  //验证提交的消息如果不以http开头则增加http

    log.debug("Updating User's Stored Message");
    String ApplicationRoot = getServletContext().getRealPath("");
    String moduleId = Getter.getModuleIdFromHash(ApplicationRoot, levelHash);
    String userId = (String)ses.getAttribute("userStamp");
    Setter.setStoredMessage(ApplicationRoot, myMessage, userId, moduleId);  // 存储用户提交的消息 到数据库core的results表中 URL字段是 resultSubmission

    log.debug("Retrieving user's class's forum");
    String classId = null;
    if(ses.getAttribute("userClass") != null)   //判断当前用户是否有userClass session 
        classId = (String)ses.getAttribute("userClass");
    String htmlOutput = Getter.getCsrfForumWithImg(ApplicationRoot, classId, moduleId, csrfGenerics); //没有userClass 则无法继续进行

    log.debug("Outputting HTML");
    out.write(htmlOutput);

Getter.getCsrfForumWithImg 实现如下

public static String getCsrfForumWithImg (String ApplicationRoot, String classId, String moduleId, ResourceBundle bundle)
{
    log.debug("*** Getter.getCsrfForum ***");
    log.debug("Getting stored messages from class: " + classId);
    String htmlOutput = new String();
    Connection conn = Database.getCoreConnection(ApplicationRoot);
    try
    {
        if(classId != null)
        {   //根据classid和moduleid 查询用户提交的消息  最终SQL  SELECT userName, resultSubmission FROM results JOIN users USING (userId) JOIN class USING (classId) WHERE classId = theClassId AND moduleId = theModuleId;  相当于一个留言板功能 
            CallableStatement callstmt = conn.prepareCall("call resultMessageByClass(?, ?)");
            log.debug("Gathering resultMessageByClass ResultSet");
            callstmt.setString(1, classId);
            callstmt.setString(2, moduleId);
            ResultSet resultSet = callstmt.executeQuery();
            log.debug("resultMessageByClass executed");

            //Table Header
            htmlOutput = "<table><tr><th>" + bundle.getString("forum.userName") + "</th><th>" + bundle.getString("forum.image") + "</th></tr>";

            log.debug("Opening Result Set from resultMessageByClass");
            int counter = 0;
            while(resultSet.next())
            {
                counter++;
                //用户提交的URL地址,会作为src地址,进行自动请求,达到一个csrf效果 
                htmlOutput += "<tr><td>" + Encode.forHtml(resultSet.getString(1)) + "</td><td><img src=\"" + Encode.forHtmlAttribute(resultSet.getString(2)) + "\"/></td></tr>"; 
            }
            if(counter > 0)
                log.debug("Added a " + counter + " row table");
            else
                log.debug("No results from query");
            //Table end
            htmlOutput += "</table>";
        }
        else
        {
            log.error("User with Null Class detected");  //没有classId 则只会报错
            htmlOutput = "<p><font color='red'>" + bundle.getString("error.noClass") + "</font></p>";
        }
    }
    catch (SQLException e)
    {
        log.error("Could not execute query: " + e.toString());
        htmlOutput = "<p>" + bundle.getString("error.occurred") + "</p>";
    }
    catch (Exception e)
    {
        log.fatal("Could not return CSRF Forum: " + e.toString());
    }
    Database.closeConnection(conn);
    log.debug("*** END getCsrfForum ***");
    return htmlOutput;
}

img src为用户提交的地址 /user/csrfchallengeone/plusplus?userid=exampleId,对应实现代码在 src/main/java/servlets/module/challenge/CsrfChallengeTargetOne.java 文件中

    ShepherdLogManager.setRequestIp(request.getRemoteAddr(), request.getHeader("X-Forwarded-For"), ses.getAttribute("userName").toString());
    log.debug(levelName + " servlet accessed by: " + ses.getAttribute("userName").toString());
    String plusId = request.getParameter("userid");
    log.debug("User Submitted - " + plusId);
    String userId = (String)ses.getAttribute("userStamp");
    if(!userId.equals(plusId)) //只要session中的用户id和传入的用户id参数不同,则可以增加别人csrf 计数
    {
        String ApplicationRoot = getServletContext().getRealPath("");
        String userName = (String)ses.getAttribute("userName");
        String attackerName = Getter.getUserName(ApplicationRoot, plusId);
        if(attackerName != null)
        {
            log.debug(userName + " is been CSRF'd by " + attackerName);

            log.debug("Attempting to Increment ");
            String moduleHash = CsrfChallengeOne.getLevelHash();
            String moduleId = Getter.getModuleIdFromHash(ApplicationRoot, moduleHash);
            result = Setter.updateCsrfCounter(ApplicationRoot, moduleId, plusId);
        }
        else
        {
            log.error("UserId '" + plusId + "' could not be found.");
        }
    }

所以这题的classid是非常关键的,只有用户存在classid才会被攻击,session中的userClass是在用户登录时进行设置的 在文件 src/main/java/servlets/Login.java 中

 String user[] = Getter.authUser(ApplicationRoot, p_login, p_pwd);
if(user != null && !user[0].isEmpty())
{

   //Kill Session and Create a new one with user logged in
   log.debug("Creating new session for " + user[2] + " " + user[1]);
   ses.invalidate();
   ses = request.getSession(true);
   ses.setAttribute("userStamp", user[0]);
   ses.setAttribute("userName", user[1]);
   ses.setAttribute("userRole", user[2]);
   ses.setAttribute("lang", language);
   log.debug("userClassId = " + user[4]);

   ses.setAttribute("userClass", user[4]);  //设置的userClass 
   log.debug("Setting CSRF cookie");
   Cookie token = new Cookie("token", Hash.randomString());
   if(request.getRequestURL().toString().startsWith("https"))//If Requested over HTTPs
       token.setSecure(true);
   response.addCookie(token);
   mustRedirect = true;

#Getter.authUser
if(userFound)
{
    //Authenticate User
    callstmt = conn.prepareCall("call authUser(?, ?)");
    log.debug("Gathering authUser ResultSet");
    callstmt.setString(1, userName);
    callstmt.setString(2, password);
    ResultSet loginAttempt = callstmt.executeQuery();
    log.debug("Opening Result Set from authUser");
    try
    {
        loginAttempt.next();
        goOn = true; //Valid password for user submitted
    }
    catch (SQLException e)
    {
        //... Outer Catch has preference to this one for some reason... This code is never reached!
        // But I'll leave it here just in case. That includes the else block if goOn is false
        log.debug("Incorrect Credentials");
        goOn = false;
    }
    if(goOn)
    {
        //ResultSet Not Empty => Credentials Correct
        result = new String[5];
        result[0] = loginAttempt.getString(1); //Id
        result[1] = loginAttempt.getString(2); //userName
        result[2] = loginAttempt.getString(3); //role
        result[4] = loginAttempt.getString(6); //classId  设置的classid  来自core.users表中 

由于admin管理员是内置账号,因此在数据库core表users中admin账号没有classid ,admin用户在界面上看不到别人发的消息,进而不会被其他人csrf,同时别人也无法看到admin发送的消息,因此admin用户基本上无法完成这个题,但是可以把admin生成的url,发送给其他用户点击,实现csrf的效果

解题步骤

按要求,提交一个完整url地址,等待别的用户查看这个页面时,触发csrf,然后攻击者这边即可看到通关密钥

总结

题目本身有点绕,核心是一个csrf攻击,需要校验请求的referer,增加token

本例中校验referer没有效果,增加随机token可以,同时特别核心功能,不建议使用GET方法进行请求

Back to top