[TOC]
The introduction
Web security system in the development of nots allow to ignore, then how can we look at our Web site, you can pay attention to the following (< www.cnblogs.com/vajoy/p/417…
-
Never trust any information that comes from the client. It should be encoded or filtered first.
-
Carefully return user input information;
-
Using blacklists and whitelisting (i.e., “what sensitive information is not allowed” or “what information is allowed only”, whitelisting is more effective but highly limited);
-
Check, validate the source of the request, and revalidate every significant operation;
-
Use SSL to prevent third parties from listening on communications (but not XSS, CSRF, SQL injection attacks);
-
Do not store important files or backup files in places accessible to the public.
-
Session ID disorder;
-
Verify the files uploaded by the user (not only format verification, for example, a GIF image should also be converted to binary and verify the color value of each frame < unsigned 8 bits > and width and height value < unsigned 16 bits >);
Account login prompt information is leaked
Vulnerability scenario
If you enter a wrong user name, you will get a clear message that the account does not exist.
By enumerating accounts, an attacker can identify security vulnerabilities
Rectification suggestions: When verifying the account and password, a vague prompt such as “Account or password error” will be provided regardless of the account or password error.
Static file content leakage after front-end packaging (e.g., static files below)
Vulnerability description: Static file stored without webpack, the page will directly request to obtain, from the browser Network can see the requested address and content of the file, if there is key information, there will be leakage problem.
Such as: http://xxxxxx/static/configUrl.js direct access to the front of the static resource file some critical information
Suggestion: Encrypt or hide sensitive information from the preceding internal IP addresses, or do not store it in the configuration file
Input limits special characters and length
Bug description: Never trust user-input information, such as regular injection scripts entered through input and then executed by the page
The rectification measures
Approach 1: El-Input and native Input for ElementUI in the VUE project
<el-input :placeholder="privateSearchPlaceholder" v-model.trim="privateValue" @keyup.enter.native="querySearchStr" clearable maxlength="100" @clear="clearSearchStr"></el-input> watch: {/** * listen for text box value changes ** / privateValue (val) {// Do not add nextTick, display the original string with special characters; This.$nextTick(() => {// Remove special characters (except alphanumeric Chinese characters) this.privateValue = val.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]+/g, ") // this.privatevalue = this.$myTools.stringFilter(val)})}Copy the code
Approach 2: El-Input for ElementUI
<el-input @input="onInput" v-model.trim="searchStr"></el-input>
methods:{
onInput(val){
console.log(val)
this.searchStr = e.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]+/g, '')
}
}
Copy the code
Note: The front end forbids special characters, but the packet capture tool can send special characters directly. Therefore, it is recommended that the key interface of the back end should also handle special characters, and SIMILAR methods can be used to solve part of SQL injection (of course, if anti-replay has been done, it can theoretically avoid capturing and resending packets).
Password Strength Requirements
Vulnerability description: In a system involving user passwords, users must limit the complexity of account passwords. Weak passwords are not allowed
Suggestion: Configure a secure password policy. For example, the password policy must contain digits, letters, and special characters and cannot be shorter than 8 characters. The following describes the function for verifying the complexity
The password contains 8 to 30 characters and must contain letters, digits, and special characters. var pwdValidate = function(rule, value, callback){ pwd = value if(! Value){return callback(new Error(' password cannot be empty '))}else{// Regular expression check password if (value.length > 8 && value.length < 30 && value.replace(/[a-zA-Z0-9]/g, '').length > 0) { if (_userAccount && (value.includes(_userAccount) || value == _userAccount)) { return callback(new Else {return callback(new Error(' Password length is 8 to 30 characters and must contain letters, digits, special characters ')); }}}Copy the code
Sensitive data encryption
Vulnerability: Key information such as user login account and password, user name, ID card, and phone number cannot be transmitted in plaintext
Rectification: use encryption processing, remember not to only use single-layer MD5 encryption, although MD5 can not be decrypted, but now the possibility of dictionary brute force cracking is increasing
(1) Salt can be added during encryption to reduce the possibility of cracking by collision; Or use symmetric or asymmetric encryption;
(2) Symmetric encryption encryption and decryption use the same key, fast, but because the key needs to be transmitted over the network, so the security is not high.
(3) Asymmetric encryption uses a pair of keys, public key and private key, so the security is high, but the encryption and decryption speed is slow.
(4) The proper solution is to encrypt the symmetric encryption key with the asymmetric encryption public key, and then send it out. The receiver uses the private key to decrypt the symmetric encryption key, and then the two parties can use the symmetric encryption to communicate.
An insecure HTTP request mode
In the information security test, HTTP methods except GET and POST are prohibited. At the beginning, I do not understand. After the query, the statement is as follows:
Why prohibit HTTP methods other than GET and POST?
What are the HTTP request methods
HTTP1.0 defines three request methods: GET, POST, and HEAD
HTTP1.1 adds five new request methods: OPTIONS, PUT, DELETE, TRACE, CONNECT
Examples of insecure HTTP methods
GET and POST are the most common methods, and most major sites only support these two methods because they meet the functional requirements. The GET method is mainly used to GET resources on the server, while the POST method is used to submit data to resources at a specific URL of the server. Other methods are disabled for security reasons, so in practice more than 90% of servers will not respond to other methods and throw a 404 or 405 error. Here are a few insecure HTTP methods:
1. The OPTIONS method will expose server information, such as middleware version and supported HTTP method.
2. PUT method. Because the PUT method does not have an authentication mechanism, it can be used to quickly and easily invade the server, upload Webshell or other malicious files, and obtain sensitive data or server permissions.
3, DELETE method, DELETE method can DELETE server specific resource files, causing malicious attacks.
It is recommended that HTTP methods other than GET and POST be disabled for two reasons:
1. Other HTTP methods except GET and POST have few rigid application scenarios and simple methods to prohibit them, that is, low implementation cost;
Once these methods are accessible to low-privileged users, they can use them to carry out effective attacks on the server, i.e. the threat is high.
Suggestion: Only GET and POST are supported
Verify file upload security
The interface involved in uploading files must be strictly checked, including format, file content, file name
CSRF cross-site request forgery – Request forgery
Features: Users must be logged in
Principle: use website vulnerabilities to automatically execute some interfaces
For example:
Defensive measures
-
Token authentication
-
The Referer in the request header validates the page source judgment
-
Hidden token Adds a hidden password to the HTTP header or request
One of them is the common practice of verifying the referer, that is, only allowing the request sent by the domain name we specify, other sites consider it as illegal request, for example, our middleware below, as long as the referer exists, and the referer is not in our domain name whitelist, then directly return 403 to refuse access.
const baseFun = require('.. /lib/baseFun'); Const whiteList = ['127.0.0.1:3000']; module.exports = function () { return async function ( ctx, next ) { if(ctx.request.headers.referer && ! whiteList.includes(ctx.request.headers.referer)){ baseFun.setResInfo(ctx, false, 'access have been forbidden', null, 403); return; } return await next(); }}Copy the code
In this case, JWT is applied. That is, when the user opens the page, the token is written into the hidden element (or hidden domain) of the page. When the interface is requested, the token is obtained from the page element and then transferred to the interface parameter. Third party sites will not be able to obtain the token without opening the page.
Examples of hidden fields such as forms are as follows:
<``input` `name``=``"authenticity_token"` `type``=``"hidden"` `value``=``"lr/g+5G/gLUzIhYpJwdtULW5afvcf8soZObMznkvxT0="` ` / >Copy the code
The reason why CSRF attack can be successful is that the hacker can completely forge the user’s request, and all the user authentication information in the request is in the cookie, so the hacker can directly use the user’s own cookie to pass the security authentication without knowing the authentication information. The key to defending against CSRF is to put information in the request that a hacker cannot forge and that does not exist in a cookie.
A token can be added as a parameter in the HTTP request header, and an interceptor can be established on the server side to verify the token. If there is no token in the request or the token content is incorrect, the request may be rejected as a CSRF attack. This is more secure than checking the Referer. Tokens can be generated after the user logs in and placed in the session, and then taken out of the session on each request and compared with the tokens in the request.
Identity certificate information leakage
Vulnerability Description: The token of identity certificate information cannot be displayed in THE URL directly, and it is better not to store in the cookie to prevent direct copying for fake operations
Suggestions: Avoid transferring important sensitive information such as user credentials directly in the URL
Tips: If the data is stored in Cookies, then Cookies can be shared as long as the service is co-domain (it is also possible to implement co-domain for multiple services through Nginx)
Session termination mechanism
Vulnerability description: After login, the session will not be automatically logged out if no operation is performed for a long time
However, if the token expires during normal operations, how to obtain the token again to ensure normal use of the user? Some policies will update the token expiration time when users continuously access the background interface. However, simply changing the token expiration time will be exploited by attacks, which is not secure.
The post-failure processing strategy generally does two things:
One is to directly jump to the login page and log in again.
The second way is: if the token is invalid, the token is refreshed automatically, and then the incomplete request operation is continued.
The first does not need to explain, in view of the second, we should consider a problem at this time, usually one page is not just to send an asynchronous request, may send multiple asynchronous requests at the same time, and multiple requests require authentication token, if one is out of date, need to refresh token, the other requests will expire after a moment, All refreshing tokens are unnecessary except for refreshing the tokens after the token is invalid for the first time.
The pseudo-code implementation is as follows:
const axios = require("axios");
// Encapsulate the request
function request(url, options) {
const token = localStorage.getItem('token');
const defaultOptions = {
headers: {
Authorization: `Bearer ${token}`,},withCredentials: true.url: url,
baseURL: BASE_URL,
};
constnewOptions = { ... options, ... defaultOptions };return axios.request(newOptions)
.then(checkStatus)
.catch(error= > console.log(error));
}
// The default refresh identifier bit
let isRefreshing = true;
function checkStatus(response) {
if (response && response.code === 401) {
// Refresh the token function, which requires adding a switch to prevent repeated requests
if(isRefreshing){
refreshTokenRequst()
}
isRefreshing = false;
// Stores the current request in the observer array
const retryOriginalRequest = new Promise((resolve) = > {
addSubscriber(() = > {
resolve(request(url, options))
})
});
return retryOriginalRequest;
}else{
returnresponse; }}function refreshTokenRequst(){
let data;
const refreshToken = localStorage.getItem('refreshToken');
data={
authorization: 'YXBwYXBpczpaSWxhQUVJdsferTeweERmR1praHk=',
refreshToken,
}
axios.request({
baseURL: BASE_URL,
url:'/classList'.method: 'POST',
data,
}).then((response) = >{
// after the refresh is complete, the refreshToken and refreshToken are stored locally
localStorage.setItem('refreshToken',response.data.refreshToken);
localStorage.setItem('token',response.data.token);
// And re-execute all requests stored in the observer array.
onAccessTokenFetched();
// Refresh the identifier bit off
isRefreshing = true;
});
}
/ / observer
let subscribers = [];
function onAccessTokenFetched() {
subscribers.forEach((callback) = >{
callback();
})
subscribers = [];
}
function addSubscriber(callback) {
subscribers.push(callback)
}
Copy the code
Subscribers can see that the refresh bits are equal to the variable isRefreshing and the observer is equal to the array subscribers. The above is the handling strategy when the token expires
Whether single sign-on is required
Description: If sso is not used, the same account can be used to log in to the system from multiple places. That is, the same account can be used to log in to the system from browser A and then from browser B, but the session of browser A is not closed.
Suggestion: If the system requires single sign-on (SSO), terminate the session at the previous location after the login succeeds at the next location
XSS (Cross Site Script) Cross-domain scripting attack — script injection
Features: Users do not need to log in
Attack principle: Inject JS script to the page
XSS, for instance:
· Write an article on sina blog and sneak in a paragraph at the same time
· Attack code, get cookies, send their own server
· Post a blog and someone reads it
· The viewer’s cookie is sent to the attacker’s server
Defensive measures
- The front end will script key code such as
- Backend substitution is also available
- ** An out-of-the-box vue.js plug-in that provides a simple way to prevent XSS attacks < github.com/hua909000/v… >
npm install vue-xss --save main.js import VueXss from "vue-xss"; Vue.use(VueXss); Vue <div :class="$style.content"> <EditorWatch :data="$XSS (topic.content)" :onlyWatch="true"></EditorWatch> </div>Copy the code
For the server interface, try not to directly spit out the data, but unified through the processing layer for transformation.
class Xss extends Controller {
index() {
const params = querystring.parse(this.ctx.request.querystring);
let name = decodeURI(params['name']);
return this.ctx.response.body = name;
//return this.resApi(true, 'good', a);
}
}
module.exports = Xss;
Copy the code
Name is returned directly to the interface without any processing, and instead of going through our resApi service, body is set directly to return *. As a result, * when name is an HTML or JS will be executed by the browser. When we start the service, we will encounter some exceptions when accessing the following addresses.
* http://127.0.0.1:3000/v1/xss/index? Name = % 3 cscript % 3 ealert (% 27 nodejs % 27) % 3 c/script % 3 e http://127.0.0.1:3000/v1/xss/index? name=%3Chtml%3E%3Ch1%3E%E6%88%91%E6%83%B3%E6%89%93%E5%8D%B0%E4%BB%80%E4%B9%88%EF%BC%8C%E5%B0%B1%E6%89%93%E5%8D%B0%E4%BB% 80%E4%B9%88%EF%BC%8C%E4%BD%A0%E7%BD%91%E7%AB%99%E8%A2%AB%E6%94%BB%E5%87%BB%E4%BA%86%3C/h1%3E%3C/html%3E
The easiest defense here is to use our unified resApi to process the response data, because the return results are fixed in resApi, json.stringify is handled, and all returns are wrapped into a JSON string, so there is no XSS problem.
支那
Also refer to below:
XSS front and back end prevention methods
Web Security series iv: XSS defense
Nodejs server rendering can be handled using the JS-XSS package < jsxss.com/zh/starter/… >
Anti-replay interface
concept
Replay Attacks refer to that an attacker sends a received packet to the target host to cheat the system. These packets are mainly used for identity authentication and undermine the correctness of authentication. It is a type of attack that repeatedly maliciously or fraudulently repeats a valid data transfer, either by the originator or by an enemy intercepting and retransmitting the data. Attackers use network monitoring or other means to steal authentication credentials, and then re-send it to the authentication server.
Encryption is effective against session hijacking, but not against replay attacks.
defense
1. The timestamp
A receives A message if and only if it contains A timestamp close enough to the current moment for A, while the timestamp being replayed will be relatively far from the current moment; But this must require that the computer clocks of the communicating parties be synchronized; Set an appropriate time window (interval). The larger the time window, the more it can contain the network transmission delay, and the smaller the time window, the more it can prevent replay attack.
However, this has a disadvantage: in the case of connection, if the two clocks are occasionally out of sync, the correct information may be misjudged as replayed information and discarded, while the incorrect replayed information may be received as the latest information
2. The serial number
The communication parties judge the freshness of the message by the serial number in the message
The communication parties are required to negotiate an initial serial number and an increment method in advance
3. Questions and responses
“Present” — one-time random number N related to the current event (do not repeat each other)
The basic approach — A expects A message from B to send B A present N in advance, and asks B to reply to A message containing N or f(N), f is A simple function agreed in advance by A and B
Principle — A determines whether the message is replayed by whether the N or F (N) replied by B is the same as that sent by itself
Take login as an example and look at specific examples
The conventional process
- Front-end Web page Users enter their accounts and passwords and click to log in.
- Before submitting the request, the Web side performs MD5 encryption on the original password through client scripts such as javascript.
- Submit the account and password after MD5
- The request is submitted to the backend to verify whether the account and password are the same as those in the database. If the account and password are the same, the login succeeds. Otherwise, the login fails.
What seems to be the problem?
The above process seems to be secure, and it is considered that the password in the transmission process is after MD5. Even if intercepted by interception, the password in plaintext will not be disclosed due to the irreversibility of MD5.
Not really! Listeners can log in without decrypting the password in plain text! Listeners simply attach the url they listen to (e.g. http://****/login.do? Method =login&password= password after MD5 & userID =login account.
A slightly safer way
- When entering the login page, a random code (called salt value) is generated and stored in the client page and session.
- When submitting a login request, the client splices the password after MD5 with the random code, executes MD5 again, and submits the request (the submitted password = MD5 (MD5 (password in plain text)+ random code)).
- After receiving the login request, the backend splices the password queried from the database with the random code in the session, performs MD5 calculation, and compares it with the result transmitted by the front-end.
Why?
In this login mode, even if the login request is monitored, the login URL is played back, and the login fails because the random codes do not match (the probability that the random codes in the listener’s session are the same as those in the monitored session can be ignored). In this login mode, because the transmitted password is the result of md5 of the original password and md5 of random code again, even if the listener uses brute force cracking, it is difficult to decrypt the plaintext password.
Md5 results of simple passwords are easy to decrypt by brute force, and MD5 has been around for so many years that there may already be many dictionaries! At the same time for the convenience of user login, our system may not require users to set a very long, very complex password! How to do? Add a fixed salt value.
- The system sets a fixed salt value, preferably complex enough, such as: 1qAZ2WSX3EDC4RFV! @#$%^&qqtrtRTWDFHAJBFHAGFUAHKJFHAJHFJHAJWRFA
- When the user registers and changes the password, the user’s original password is spliced with our fixed salt value, and then md5 calculation is performed.
- The password saved in the database is the result of MD5 calculation after the user’s original password is spliced with a fixed salt value.
- When logging in, the user’s original password is spliced with our fixed salt value, and then the MD5 operation is performed. The result of the operation is spliced with our random code, and then the MD5 operation is performed again, and then the submission is completed.
- After receiving the login request, the backend splices the password queried from the database with the random code in the session, performs MD5 calculation, and compares it with the result transmitted by the front-end.
In a further step
- Add login verification code, can prevent artificial violent login cracking
- Lock an account. If a user enters incorrect passwords for a certain number of times (for example, six times), the account can be locked
Use the timestamp + random number as the anti-replay method of the actual code:
Front end:
const crypto = require('crypto'); const md5 = require('md5'); Var AES_conf = {key: "ess-2019 $05#sb%_", '1012132405963708' // offset vector} export default{/** * AES_128_CBC encryption * 128 bits * return base64 */ encryption:(data) => {let key = AES_conf.key; let iv = AES_conf.iv; var cipherChunks = []; var cipher = crypto.createCipheriv('aes-128-cbc', key, iv); cipher.setAutoPadding(true); cipherChunks.push(cipher.update(data, 'utf8', 'base64')); cipherChunks.push(cipher.final('base64')); return cipherChunks.join(''); }, /** * decryption * return utf8 */ decryption:(data) => {let key = aes_conf.key; let iv = AES_conf.iv; var cipherChunks = []; var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); decipher.setAutoPadding(true); try { cipherChunks.push(decipher.update(data, 'base64', 'utf8')); cipherChunks.push(decipher.final('utf8')); return {code: 200, data: cipherChunks.join('')}; } catch (error) {return {code: 400, data: 'ciphertext error, decryption failed '}; }}, // generateIntegrityVal(data){return MD5 (json.stringify (data))}, // data integrityChecke(data, IntegrityValue){return (md5(json.stringify (data)) === integrityValue)}, // Generate a range of random number random(start, end) { return parseInt(start + Math.random() * (end - start)) }, /** * @description: GenerateSign () {let timestamp = new Date().gettime (), nonce = parseInt(this.random(0,100000000)), Sign = md5(timestamp + nonce); let val = { timestamp, nonce, Sign} return JSON. Stringify (val)}} axios interceptors / / HTTP request interceptor axios. Interceptors. Request. Use request (= > {/ / prevent replay signature request.headers['sign'] = tools.generateSign() return request; }, err => {} );Copy the code
Node ExpressJS server code
/ / CSRF: App. use(function (req, res, next) { let referer = req.get('Referrer') || req.headers.referer || req.headers.host logger.info('referer:', referer) if (! referer) { next({status: 403,message: "Your request was Forbidden"}) // next() // referer does not exist or can be directly skip} else {// request header parameters are fault-tolerant (referer.startsWith('http://')) { referer = referer.substring(7) } if (referer.endsWith('/')) { referer = referer.substring(0, referer.length - 1) } console.log('endReferer',referer) if (config._systemConf.safeReferers.includes(referer)) { next(); } else { next({status: 403,message: "Your request was Forbidden!" }}}})); Var signMap = tools.signMap; / / the replay function app. Use ((the req, res, next) = > {/ / static files do not prevent replay the if (the req. Path = = '/' | | view interceptFilter (the req, [' JPG ', '. PNG ', '.ico','.icon', '.html', '.jpeg', 'dist', '.js', '.css'])){ next() }else{ if(! req.headers.sign){ next({status: 403,message: "Your request was Forbidden!!" })}else{// Get sign object let signObj = json.parse (req.headers); Let sign = md5(signobj.timestamp + signobj.nonce); Logger. info(' Replay time: ',(new Date().getTime() - signObj.timestamp), Config._systemconf. ReplayTime) // Check whether it is consistent with the sign generated by the front end, check whether the request interval is less than 60 seconds, and check whether the sign does not exist in the signMap array. If (signObj && sign == signObj.sign && (new Date().getTime() - signObj.timestamp) < config._systemConf.ReplayTime && signMap.every(item => item.sign ! = sign)) { signMap.push(signObj); next() } else { next({status: 403,message: "Your request was Forbidden!!!" }) } } } }) tools.js /** * Created by Administrator on 2017/6/21. */ "use strict" const crypto = require('crypto'); const md5 = require('md5'); Var AES_conf = {key: "ess-2019 $05#sb%_", // key iv: '1012132405963708' // offset vector} module.exports = {/** * AES_128_CBC encryption * 128 bits * return base64 */ encryption: (data) => { let key = AES_conf.key; let iv = AES_conf.iv; var cipherChunks = []; var cipher = crypto.createCipheriv('aes-128-cbc', key, iv); cipher.setAutoPadding(true); cipherChunks.push(cipher.update(data, 'utf8', 'base64')); cipherChunks.push(cipher.final('base64')); return cipherChunks.join(''); // return cipherchunk.join ('').replace(new RegExp('=','g'),'')}, /** * return utf8 */ decryption: (data) => { // let add = data.length%3; // for(let i = 0 ; i<add ; i++){ // data+='=' // } let key = AES_conf.key; let iv = AES_conf.iv; var cipherChunks = []; var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); decipher.setAutoPadding(true); try { cipherChunks.push(decipher.update(data, 'base64', 'utf8')); cipherChunks.push(decipher.final('utf8')); return { code: 200, data: cipherChunks.join('') }; } catch (error) {return {code: 400, data: 'ciphertext error, decryption failed'}; }, // data integrityChecke(data, integrityValue) {return (md5(json.stringify (data)) === integrityValue)}, * @param {start} * @param {end} * @return: */ random(start, end) { return parseInt(start + Math.random() * (end - start)) }, /** * @description: */ signMap: [], generateSign() {let timestamp = new Date().gettime (), nonce = parseInt(this.random(0,100000000)), Sign = md5(timestamp + nonce); Return {timestamp, nonce, sign}}, /** ** interceptFilter(req, filterArr){let ifFilter = false; For (let filter of filterArr){if(req.path.includes(filter)){ifFilter = true ifFilter } };Copy the code
Interface data integrity
Key interfaces such as user login and password change are required
You can add an integrity field to the request header or store it as a separate parameter, as shown below
GenerateIntegrityVal (data){return MD5 (json.stringify (data))}, --------------------------------------------------------- updatePassword: function (userID, oldPwd, newPwd, callback) { let _params = { id: userID, oldPwd: Tools. encryption(oldPwd), // newPwd: Tools. Encryption (newPwd)} let integrityValue = view generateIntegrityVal (_params) / / data integrity MD5 fields generated, do check the backend axios.post('/uums/users/updatePassword', { userId: _params.id, oldPwd: _params.oldPwd, newPwd: _params.newPwd, integrity: integrityValue }).then((res) => { callback(res.data); }); }Copy the code
SQL injection
< www.zhihu.com/question/22… >
Examples of injection methods:
Select * from tbl_topic_info where topic_id = ${id} 1 Select * from tbl_topic_info where topic_id = ${id Select * from topic_id where topic_id = '1'; SELECT pg_sleep(5)-- select * from tbl_topic_info where topic_id = '1'; SELECT pg_sleep(5) from pg_sleep(5) where 1 = 1; select * from tbl_topic_info where topic_id = '1' or 1 = 1 --; 'So you get all the data and you get data leaksCopy the code
Program, whoever has the SQL injection vulnerabilities are program to accept from the client user input variables or URL parameter, and the variable or parameter is part of the SQL statement, or transfer the contents of this user input parameters, we should be vigilant, this is the safety in the field of the principle of “external data do not trust”, Looking at the various attacks in the Web security field, most of them are caused by developers violating this principle, so it is natural to start with variable detection, filtering, and verification to ensure that the variables are what the developer intended.
Never trust variable input from the client. If a variable has a fixed format, the corresponding format must be strictly checked. If a variable does not have a fixed format, special characters such as quotation marks must be filtered and escaped.
1. Check the variable data type and format
2. Filter special symbols
Bind variables, using precompiled statements
One of the most common methods used in Node.js is to use placeholders (i.e., precompiled statements) instead of assembling SQL syntax
connection.query('SELECT * FROM student WHERE name = ? ', [name], function(err, results) {})Copy the code
The nodeJS Sequelize library anti-injection tests can be found below:
Blog.csdn.net/bbhe_work/a…
updateEliteStatusByTopicId: function (id, flag) { return sequelize .query( `update tbl_topic_info set elitestatus = $flag where topic_id = $id`, {type: sequelize. QueryTypes. UPDATE, / / specified SQL to SELECT the bind: {flag, flag, id: id, }, } ) .then((result) => { return result; }) .catch((err) => { logger.error( tools.getFileName(__filename) + "::updateEliteStatusByTopicId::" + err ); throw err; }); }, // getTopicByConSql: function (params) {let conSql = "; let conArr = []; let bindObj = {}; if (params.topicTypeId) { conArr.push(" type_id = $topicTypeId "); bindObj.topicTypeId = params.topicTypeId; } if (params.searchStr) { conArr.push( `topic_title like '%$searchStr' or topic_txt like '%$searchStr%' ` ); bindObj.searchStr = params.searchStr; } if (params.startDate) { conArr.push(`create_time > $startDate `); bindObj.startDate = params.startDate; } if (params.endDate) { conArr.push(`create_time < $endDate `); bindObj.endDate = params.endDate; } if (conArr.length > 0) { for (let i in conArr) { if (i == 0) { conSql = "where " + conArr[i]; } else { conSql = conSql + " and " + conArr[i]; } } } return sequelize .query( // `select * from tbl_topic_info where type_id = $topicTypeId `, `select * from tbl_topic_info ${conSql} order by elitestatus desc, create_time desc`, { type: Sequelize. QueryTypes. SELECT, / / specified SQL to SELECT the bind: bindObj,}), then ((result) = > {return result; }) .catch((err) => { logger.error( tools.getFileName(__filename) + "::getTopicByConSql::" + err ); throw err; }); },Copy the code
Database information encryption security
Believe everyone still vividly remember the CSDN drag library events reported in 2011, it was lambasted lead to CSDN at the forefront of the reason is that they definitely store a user’s password, that sparked the technology to the user information security, especially a strong focus on password security, we in the defense against SQL injection at the same time, And we should plan ahead. You might be the next one to get hauled. Who knows.
In Web development, traditional encryption and decryption can be roughly divided into three types:
1, symmetric encryption:
That is, both the encryption party and the decryption party use the same encryption algorithm and key. In this scheme, the preservation of the key is very critical, because the algorithm is public and the key is secret. Once the key is leaked, the hacker can still easily decrypt it. Common symmetric encryption algorithms include AES and DES.
2, asymmetric encryption:
That is, different keys are used for encryption and decryption. Keys are divided into public keys and private keys. Data encrypted with the private key must be decrypted using the public key, and data encrypted with the public key must be decrypted using the corresponding private key.
3, irreversible encryption:
The hashing algorithm is used to make the data cannot be decrypted back to the original data after encryption. The commonly used hashing algorithms include MD5 and SHA-1.
However, MD5 has been commonly used to determine a different password for each user and add salt, and then mix the user’s real password for MD5 encryption
Dos
The main principle of this kind of network attack is to cause the crash of background service by simulating invalid massive user requests. Usually don’t need to worry about in this kind of service in the background, can be directly in the gateway layer for processing, if really no gateway layer, consider using a ddos library of NPM (www.npmjs.com/package/ddo)… .
Some issues that are unique to Node.js
The eval function
In any case, use of this function should be avoided because it has very uncontrollable elements, similar to SQL injection and JS code injection, such as the following code:
const querystring = require('querystring'); const Controller = require('.. /.. /core/controller'); class Eval extends Controller { index() { const params = querystring.parse(this.ctx.request.querystring); DecodeURI (params['r']); decodeURI(params['r']); Let ret = eval(' this._q() + ${r} '); let ret = eval(' this._q() + ${r} '); return this.resApi(true, 'good', ret); } _q () { return 1; } _p () { return 2; } } module.exports = Eval;Copy the code
The code is relatively simple. Suppose we want to use eval to dynamically call some internal function, so we use the parameter r. Normally, it can be called, but if we call the following address:
(http://127.0.0.1:3000/v1/eval/index?r=this._p); console.log('d'); const fs=require('fs'); fs.readFileSync(__filename, 'utf8')Copy the code
After you access it, you’ll find the scary thing: the source code is returned directly, as shown in Figure 1.
Figure 1 An example of eval leaking source code
Therefore, under no circumstances should eval be used in code, because the uncontrollable factors are too large.
File to read and write
The path problem was explained earlier when we designed the route, as shown in this code:
// Remove unconventional request paths and convert - to uppercase pathName = pathname.replace('.. ', '').replace(/\-(\w)/g, (all,letter)=>letter.toUpperCase());Copy the code
The first two points of replace are very important, so that we can control that the require file is only in the Controller folder.
Let’s look at a problem caused by not controlling directory paths properly, such as the following code:
class Fs extends Controller { index() { const params = querystring.parse(this.ctx.request.querystring); Let product = decodeURI(params['product']); try { let productInfo = fs.readFileSync(`${__dirname}/.. /.. /config/products/${product}.json`, 'utf8'); return this.resApi(true, 'good', productInfo); } catch(err){ return this.resApi(false, 'can not find the product'); }}}Copy the code
Normal access to the following two links can get our specific needs of the normal logic.
http://127.0.0.1:3000/v1/fs/index?product=c http://127.0.0.1:3000/v1/fs/index?product=d normal return results as follows: {code: 200. MSG: '', data: "{ name: 'john', age: 23 }" }Copy the code
However, if we access the following address, we directly cause the configuration file to leak, which raises the security issue of database account and password leakage.
http://127.0.0.1:3000/v1/fs/index?product=.. {code: 200. MSG: ", data: "{database: 'XXX ', user: 'lilei', pwd:'xxx12334dd&' }" }Copy the code
The solution is to control the access to configuration files in the current configuration file directory, so you need to put this kind of.. Path substitution, such as using the following code fix, solves the problem. When you start the service again and access the above path, you will be prompted with an access path exception message.
// 去掉上层目录访问
product = product.replace('..', '')
Copy the code
Note, however, that you can still access files in the same directory, so the best way is to classify configuration files and verify them. Configuration files that are not in the scope are not allowed to be read.
Secondly, when writing files, we should pay more attention to risks. In general, we should write directories and source code directories separately. For example, we can put uploaded files or log files in another separate directory and control permissions. In case of code writing bugs that lead to local files being tampered with, or script files being written to take control of the server.
Permission of non-root user
In most cases, node.js processes do not require too many permissions, only some fixed directory read and write permissions, so we only need to give node.js service the lowest user permissions, do not set root permissions. For example, if you have root permission, you can use Node.js fs to get the host login password and control the machine directly. In most companies, hosts communicate with hosts on the Intranet. If a single Intranet machine is conquered, it is equivalent to the loss of the entire Intranet system of the company.
To solve this problem, create a separate user and grant read and write permissions to the log and other directory permissions required by Node.js, as shown below:
adduser username
chown -R /path
Copy the code
The first step is to create a user, and the second step is to own the user. In general, you only need to own the current source code path and the directory to write logs.
This section is derived from the retractor education curriculum “Nodejs application development of actual combat” : kaiwu.lagou.com/course/cour…
Url address obfuscation encryption
When the client generates a request, the INTERFACE URL is encrypted with RSA.
Suppose we wanted to access an interface like api.example.com/articles, which returns JSON data. Before the client accesses the url, we do something like this:
- Add client timestamp: api.example.com/1322470148/…
- The url path for rsa encryption, then base64:api.example.com/TBhIskCgCN+…
The actual url becomes this long url structure. Using the PADDING parameter and timestamp of the RSA algorithm, we can make this long BAS64 string change every time we access it. Meanwhile, we can write down all the requested strings within an hour on the server side. Reaccesses are not allowed, which prevents the crawler from trying to replay the request.
On the server side, we need to restore the URL and return the response data before doing the response.