本文是全系列中第5 / 5篇:从零开始做渗透

来源 https://www.freebuf.com/vuls/177923.html

最近在研究OWASP WebGoat8.0,鲜鲜实验室有一篇文章特别好,从安装到攻略都有(传送)。但其中有几关这位大佬没有做出答案(但我做出来了),当然我在网上找了一些攻略也没有这几关的具体答案,所以把自己做的答案贴出来,给也在研究这个靶场的兄弟们作参考。

最后挑战WithoutAccount
这道题就是最后一关,页面的意思是投票,点击星星来决定投几星。

1.png

有意思的是,当我们点击星星之后提示我们需要登录才能投票,但这个页面并没有登录按钮。

我们来查看后台源码:

@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 json = Maps.newHashMap();

       json.put("error", true);

       json.put("message", "Sorry but you need to login first inorder 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 forvoting, your flag is: " + Flag.FLAGS.get(8)).build();

}

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

2.png

失败了。

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

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

SQL Injection(mitigation)
这道题的意思是我们可以对下面四个主机排序,可以根据hostname,ip,mac,status,description等,对应的山下箭头就可以排序,但需要注意的是,下面的提交框不存在注入(题目也说明了)。最后我们要找到主机“webgoat-prd”的ip地址。

4.png

没什么好说的,直接抓包查看数据提交方式:
5.png

发现是get提交,对排序的参数直接写在后面了。我们放入repeater进行重放,查看相应包:
6.png

右侧就是排序的数据,目前已经按ip排序好了,我们可以尝试修改参数查看是否能排序:
7.png

可见已经按照id改好了,但id选项并没有在网页上。我们再根据提示简单构造一个逻辑语句:
8.png

可见已经按照ip排序了,我们将true换成false试试:
9.png

变成了根据ip排序,所以我们可以将true和false的位置换成逻辑语句,此处存在order by布尔盲注。WebGoat用的是HSQLDB,这个数据库貌似并没有类似mysql的information_schema库,所以表名只能暴力破解,使用逻辑语句:

exists(select * from table_name)
table_name就是需要暴力破解的地方。这道题的表是servers,相比不算难破解。(至少使用字典使用牛津的那本就可以,手动滑稽)

接下来直接构造逻辑语句就好

/WebGoat/SqlInjection/servers?column=(CASE+WHEN+((select+left(ip,1)+from+servers+where+hostname=’webgoat-prd’)=’1′)+THEN+id+ELSE+description+END)
算是传统的布尔注入payload,但注意的是left要放在ip前面,而不是整个select语句前面。最后拆解出的答案是104.130.219.202

SQL Injection(advanced)
这关也是最后challenge的第三关,是让我们使用tom的账户登录。但除了用户名是tom意外什么也不知道。但给了我们一个注册页面。首先查看后台的语句,登录部分:

private static final String PASSWORD_TOM = “thisisasecretfortomonly”;

//Make it more random at runtime (good luck guessing)

private static final String USERS_TABLE_NAME = "challenge_users_6" + RandomStringUtils.randomAlphabetic(16);

@Autowired

private WebSession webSession;

public SqlInjectionChallenge() {

    log.info("Challenge 6 tablename is: {}", USERS_TABLE_NAME);

}

源码中直接写出了tom的密码,可以直接提交成功过关。但如果想sql注入出密码的话需要费些功夫。

@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;

}

@RequestMapping(method = POST)

@ResponseBody

public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {

    Connection connection = DatabaseUtilities.getConnection(webSession);

    checkDatabase(connection);

    PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = ? and password = ?");

    statement.setString(1, username_login);

    statement.setString(2, password_login);

    ResultSet resultSet = statement.executeQuery();

    if (resultSet.next() && "tom".equals(username_login)) {

        return success().build();

    } else {

        return failed().feedback("NoResultsMatched").build();

    }

}

可以看见,登录过程和注册信息入库过程都启用了预编译,几乎没有什么注入可能,唯一有注入点的地方就是检查用户是否注册过这里,直接把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了

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注