1012.Web安全攻防靶场之WebGoat – 3

概述

这是 WebGoat 的最后一部分,主要内容是 WebGoat中的Challenge,前面还有 12

Challenge

Admin lost password

本题目的服务端源代码。

@AssignmentPath("/challenge/1")
public class Assignment1 extends AssignmentEndpoint {

    @RequestMapping(method = RequestMethod.POST)
    public
    @ResponseBody
    AttackResult completed(@RequestParam String username, @RequestParam String password, HttpServletRequest request) throws IOException {
        boolean ipAddressKnown =  true;
        boolean passwordCorrect = "admin".equals(username) && PASSWORD.equals(password);
        if (passwordCorrect && ipAddressKnown) {
            return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(1)).build();
        } else if (passwordCorrect) {
            return failed().feedback("ip.address.unknown").build();
        }
        return failed().build();
    }

    public static boolean containsHeader(HttpServletRequest request) {
        return StringUtils.hasText(request.getHeader("X-Forwarded-For"));

    }
}

public interface SolutionConstants {

    //TODO should be random generated when starting the server
    String PASSWORD = "!!webgoat_admin_1234!!";
    String PASSWORD_TOM = "thisisasecretfortomonly";
    String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2";
}

可以看到是直接用两个输入和两个字符串常量做匹配,然后做了一个与操作。感觉无法绕过???

Without password

此题目要求用账户Larry登录,这道题目是一道万能密码题目。

使用Larry/1' or '1'=1进行登录就可以了,看一下题目源代码

@RequestMapping(method = POST)
@ResponseBody
public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {
    Connection connection = DatabaseUtilities.getConnection(webSession);
    checkDatabase(connection);

    if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) {
        return failed().feedback("required4").build();
    }
    if (!"Larry".equals(username_login)) {
        return failed().feedback("user.not.larry").feedbackArgs(username_login).build();
    }

    PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = '" + username_login + "' and password = '" + password_login + "'");
    ResultSet resultSet = statement.executeQuery();

    if (resultSet.next()) {
        return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(5)).build();
    } else {
        return failed().feedback("challenge.close").build();
    }
}

可以看到题目直接将接收到的用户名和密码代入了sql语句,所以使用万能密码后的拼好的完整sql语句为

select password from  USERS_TABLE_NAME  where userid = 'Larry' and password = '1' or '1'='1'

此语句永远为为真,就登录了。

Creating a new account

在注册界面抓包后发现链接为 WebGoat/challenge/6,查看源文件

@PutMapping  //assignment path is bounded to class so we use different http method :-)
@ResponseBody
public AttackResult registerNewUser(@RequestParam String username_reg, @RequestParam String email_reg, @RequestParam String password_reg) throws Exception {
    AttackResult attackResult = checkArguments(username_reg, email_reg, password_reg);

    if (attackResult == null) {
        Connection connection = DatabaseUtilities.getConnection(webSession);
        checkDatabase(connection);

        String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'";
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(checkUserQuery);

        if (resultSet.next()) {
            attackResult = failed().feedback("user.exists").feedbackArgs(username_reg).build();
        } else {
            PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO " + USERS_TABLE_NAME + " VALUES (?, ?, ?)");
            preparedStatement.setString(1, username_reg);
            preparedStatement.setString(2, email_reg);
            preparedStatement.setString(3, password_reg);
            preparedStatement.execute();
            attackResult = success().feedback("user.created").feedbackArgs(username_reg).build();
        }
    }
    return attackResult;
}

发现在String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'";中直接把userid插入了查询用户是否存在语句,则直接用sqlmap进行注入测试。测试包如下。

PUT /WebGoat/challenge/6 HTTP/1.1
Host: 127.0.0.1:8080
Content-Length: 84
Accept: */*
Origin: http://127.0.0.1:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://127.0.0.1:8080/WebGoat/start.mvc
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: JSESSIONID=A7D0A7096B5E9685DF152BC343A54B8E
Connection: close

username_reg=tom*&email_reg=Tom%40Tom.com&password_reg=tom&confirm_password_reg=tom1

可以看见,登录过程和注册信息入库过程都启用了预编译,几乎没有什么注入可能,唯一有注入点的地方就是检查用户是否注册过这里,直接把username_reg拼接在sql语句中,我的用户名是breeze,再次注册会提示我已经注册过,但我如果把用户名改为breeze’ and1=2 –就会提示我创建账户成功。这样我们就可以在and后构造逻辑语句来进行布尔注入了。但问题是,如何知道表名。 
10.png

在源码中,我们看出,这张表每次使用都会创建新的,用完删除,而表名是challenge_users_6加上随机生成的16位长度的字符串,几乎不可能暴力破解了。但它讲表名输出到了服务器的log上,所以我们可以去log查看本次的表名 
11.png

这次的表名是challenge_users_6WDzKXNcjaYiNPkSr,根据这个表名构造逻辑语句,前面的用户我们使用没有注册过的breeze123,那么查询结果就是假,后面使用or+逻辑语句,这样我们的逻辑语句是真就会返回假,是假就会返回真。

逻辑语句:

 

breeze123'+or+(select+left(password,1)+from+challenge_users_6WDzKXNcjaYiNPkSr+where+userid='tom')='a'+--

 

12.png13.png

写一个脚本就可以得到密码 thisisasecretfortomonly了

Admin password reset

还是老问题,收不到邮件,这个题目是让重置admin用户的密码,当输入邮箱后在WebWolf里什么也看不到,后发现发邮件时必须包含你当前的用户名,比如我的用户名是admin1,则应该给用户admin1@xxx.xxx发送。

接受到邮件后,点击reset链接。

说不是admin用户,则说明此题我们需要构造出用户admin的重置链接。在题目链接下测试了一下.git文件后,链接http://127.0.0.1:8080/WebGoat/challenge/7/.git,打开发现是git的包,则回复使用一下。

将下载的git文件解压,然后打开命令开,使用git status来看一下状态。

这里获取文件后,可以通过jd-gui反编译PasswordResetLink.class看到源代码。

/**
 * WARNING: DO NOT CHANGE FILE WITHOUT CHANGING .git contents
 *
 * @author nbaars
 * @since 8/17/17.
 */
public class PasswordResetLink {

    public String createPasswordReset(String username, String key) {
        Random random = new Random();
        if (username.equalsIgnoreCase("admin")) {
            //Admin has a fix reset link
            random.setSeed(key.length());
        }
        return scramble(random, scramble(random, scramble(random, MD5.getHashString(username))));
    }

    public static String scramble(Random random, String inputString) {
        char a[] = inputString.toCharArray();
        for (int i = 0; i < a.length; i++) {
            int j = random.nextInt(a.length);
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return new String(a);
    }

    public static void main(String[] args) {
        if (args == null || args.length != 2) {
            System.out.println("Need a username and key");
            System.exit(1);
        }
        String username = args[0];
        String key = args[1];
        System.out.println("Generation password reset link for " + username);
        System.out.println("Created password reset link: " + new PasswordResetLink().createPasswordReset(username, key));
    }
}

关键代码在createPasswordReset中,可以发现是admin用户时,会把一个key的长度传进Random函数里当做种子进行计算,所以就好办了,按照createPasswordReset的算法,将key的长度多尝试几次,如1-30,然后使用点击访问链接就可以了。

经过实际的工作,写来一段代码来看不同值的hash值。

发现当用户为admin是,上图两个红框内的内容是一样的,按道理说是符合题目要求的,但是webgoate官方给出的hash为375afe1104f4a487a73823c50a9292a2,应该是答案出了问题。

Without account

本题目需要用户进行投票,如果成功,则功能通过,但是需要登录后才能进行投票。

下面是获取flag的关键代码

    @GetMapping(value = "/vote/{stars}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<?> vote(@PathVariable(value = "stars") int nrOfStars, HttpServletRequest request) {
        //Simple implementation of VERB Based Authentication
        String msg = "";
        if (request.getMethod().equals("GET")) {
            HashMap<String, Object> json = Maps.newHashMap();
            json.put("error", true);
            json.put("message", "Sorry but you need to login first in order to vote");
            return ResponseEntity.status(200).body(json);
        }
        Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0);
        votes.put(nrOfStars, allVotesForStar + 1);
        return ResponseEntity.ok().header("X-Flag", "Thanks for voting, your flag is: " + Flag.FLAGS.get(8)).build();
    }

查看了源码后发现,只要是GET请求都会返回失败。但这个GetMapping就是get提交的,所以我的思路是,使用其他方法提交请求绕过,先将GET改为POST提交。

2.png

失败了。

然后换成PUT,还是失败,之后换成HEAD,发现了flag。(就是这么简单)

3.png

所以这道题主要还是考对http协议的熟悉,假如就只知道提交方式GET,POST,PUT是做不出来的。

posted @ 2019-02-17 23:48  一切刚刚开始  阅读(1105)  评论(0编辑  收藏  举报