构建安全的Web应用


sql注入

下述表单提交,不知道用户密码也能登录成功

login-sql注入

成因:

登录过程中进行数据库查询操作时,没有对参数进行检查或转义

1
2
3
def get_user2(name, passwd):
    sql = "SELECT * FROM user WHERE name='%s' and password='%s'"% (name, passwd)
    return db_exec(sql)

修复

使用execute拼接sql,既对参数进行转义

1
2
3
def get_user2(name, passwd):
    # 对字符串转义
    return db_exec("SELECT * FROM user WHERE name=%s and password=%s", (name, passwd))

why:

MySQLdb中对字符串进行转义,因此sql执行器不会去解析字符串


xss攻击

下述表单提交后,重载页面就会弹出’xss’的框

xss

成因:

index渲染的时候没有对用户输入的数据做检查或转义

1
2
3
4
5
6
def render(file_path, data={}):
    encoding = 'utf-8'
    data = to_unicode(data, encoding)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'))
    template = env.get_template(file_path)
    return template.render(**data).encode(encoding)

修复:

jinja2渲染模版开启自动转义(autoescape=True)

1
2
3
4
5
6
7
def render(file_path, data={}):
    encoding = 'utf-8'
    data = to_unicode(data, encoding)
    # 开启自动转义
    env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'), autoescape=True)
    template = env.get_template(file_path)
    return template.render(**data).encode(encoding)

why

转义之后浏览器不会去解析html标签或js代码等,因此可以防止用户内嵌其他页面(如广告),或提交有风险的js脚本


csrf攻击

在攻击页面写入以下代码,若用户在此前已成功登录demo站点,则会自动向bob账户转账200

1
2
3
4
5
6
7
<form id="form" action="http://www.demo.com/transfer" method="POST" style="display:none;">
    <input type="text" name="receiver" value="bob"/>
    <input type="text" name="amount" value="200"/>
</form>
<script>
    document.getElementById("form").submit();
</script>

成因

在transfer接口没有对请求做任何校验

1
2
3
4
5
6
7
8
9
class Transfer(lib.AuthHandler):
    def post(self):
        receiver = self.request.get('receiver')
        amount = self.request.get('amount')
        if receiver and amount:
            amount = int(amount)
            name = self.session.get('user')
            db.transfer(name, receiver, amount)
        return self.redirect('/home')

修复

app/home.py

在访问表单页时记录当前的时间戳到cookie中,并在提交表单时校验ticket

 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
class Home(lib.AuthHandler):
    def get(self):
        user = db.get_user(self.session.get('user'))

        # cookie中添加一个时间戳,用session记录
        t = str(time.time())
        self.response.set_cookie('time', t)
        self.session.set('time', t)
        self.session.save()

        data = {'_user': user}
        return self.respond('home.html', data)
      
class Transfer(lib.AuthHandler):
    def post(self):
        receiver = self.request.get('receiver')
        amount = self.request.get('amount')
        ticket = self.request.get('ticket')

        # 检查正整数
        if receiver and lib.check_int(amount):
            amount = int(amount)
            name = self.session.get('user')

            # 检查ticket
            time = self.session.get('time')
            if lib.check_ticket(time, ticket):
                db.transfer(name, receiver, amount)
        return self.redirect('/home')

common/lib.py

相关检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 检查正整数
def check_int(value):
    try:
        value = int(value)
        if value < 0:
            raise
    except:
        return False
    return True

# 检查ticket
def check_ticket(time, ticket):
    if time and time + 'haha' == ticket:
        return True
    return False

templates/home.html

在表单中增加一个ticket,ticket由在cookie里记录的时间戳生成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<input type="hidden" name="ticket" id="ticket" value="">
<script>
   function getCookie(name) {
      var cookieName=encodeURIComponent(name) + '=';
      var cookieStart = document.cookie.indexOf(cookieName);
      var cookieValue = null;
      if (cookieStart > -1){
         var cookieEnd = document.cookie.indexOf(';', cookieStart);
         if (cookieEnd == -1) {
            cookieEnd = document.cookie.length;
         }
         cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length,cookieEnd));
      }
      return cookieValue
   }
   document.getElementById("ticket").value = getCookie('time') + 'haha';
</script>

why

攻击者要求用户访问demo站点时虽然可以携带cookie,但攻击者并不能获取cookie,所以无法生成有效的ticket