zoukankan      html  css  js  c++  java
  • 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是做不出来的。

  • 相关阅读:
    防火墙iptables 设置
    CentOS 6.5系统中安装配置MySQL数据库
    判断服务是否开启,应用是否安装,并安装应用
    判断是移动端还是PC端
    二维码的生成细节和原理
    onclick 常用手册
    PHP json_encode函数中需要注意的地方
    利用PHP SOAP扩展实现简单Web Services
    Symfony2学习笔记之事件分配器
    听 Fabien Potencier 谈Symfony2 之 《What is Symfony2 ?》
  • 原文地址:https://www.cnblogs.com/beijibing/p/10393326.html
Copyright © 2011-2022 走看看