From 807d48063ae0db7ea92d95ee42b90dc4fdee22f2 Mon Sep 17 00:00:00 2001 From: jo-hnny Date: Thu, 8 Dec 2022 19:27:01 +0800 Subject: [PATCH] fix(console): add csrf check (#2198) * fix(console): add csrf check * fix(console): webpack support csrf-code * feat(console): change DJB hash to md5 * fix(console): add csrf token cache --- pkg/gateway/token/token.go | 23 ++++++++++++++++++++--- web/console/helpers/csrf.ts | 15 +++++++++++++++ web/console/helpers/index.ts | 1 + web/console/helpers/reduceNetwork.ts | 5 ++++- web/console/package-lock.json | 18 +++++++++++++++--- web/console/package.json | 5 ++++- web/console/src/webApi/request.ts | 5 ++++- web/console/webpack/csrf.js | 28 ++++++++++++++++++++++++++++ web/console/webpack/webpack.dev.js | 3 ++- 9 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 web/console/helpers/csrf.ts create mode 100644 web/console/webpack/csrf.js diff --git a/pkg/gateway/token/token.go b/pkg/gateway/token/token.go index 23d195805..de54c7915 100644 --- a/pkg/gateway/token/token.go +++ b/pkg/gateway/token/token.go @@ -19,17 +19,22 @@ package token import ( + "crypto/md5" "encoding/base64" + "encoding/hex" "fmt" - jsoniter "github.com/json-iterator/go" - "golang.org/x/oauth2" "net/http" + "strings" "time" + + jsoniter "github.com/json-iterator/go" + "golang.org/x/oauth2" "tkestack.io/tke/pkg/util/log" ) const ( cookieName = "tke" + headerName = "X-CSRF-TOKEN" ) var json = jsoniter.ConfigCompatibleWithStandardLibrary @@ -47,6 +52,18 @@ func RetrieveToken(request *http.Request) (*Token, error) { if err != nil { return nil, err } + + // if is api request, check csrf + path := request.URL.Path + if strings.HasPrefix(path, "/api") { + csrfHeader := request.Header.Get(headerName) + cookieHash := md5.Sum([]byte(cookie.Value)) + + if csrfHeader != hex.EncodeToString(cookieHash[:]) { + return nil, fmt.Errorf("invalid CSRF token") + } + } + tokenJSON, err := base64.StdEncoding.DecodeString(cookie.Value) if err != nil { log.Error("Failed to base64 decode cookie value", log.Err(err)) @@ -87,7 +104,7 @@ func ResponseToken(t *oauth2.Token, writer http.ResponseWriter) error { cookie := &http.Cookie{ Name: cookieName, Value: tokenStr, - HttpOnly: true, + HttpOnly: false, Secure: false, Path: "/", MaxAge: int(time.Until(t.Expiry).Seconds()), diff --git a/web/console/helpers/csrf.ts b/web/console/helpers/csrf.ts new file mode 100644 index 000000000..a3fa5c46f --- /dev/null +++ b/web/console/helpers/csrf.ts @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie'; +import SparkMD5 from 'spark-md5'; + +let CSRF_TOKEN = null; + +export function createCSRFHeader() { + if (CSRF_TOKEN === null) { + const tkeCookie = Cookies.get('tke') ?? ''; + CSRF_TOKEN = SparkMD5.hash(tkeCookie); + } + + return { + 'X-CSRF-TOKEN': CSRF_TOKEN + }; +} diff --git a/web/console/helpers/index.ts b/web/console/helpers/index.ts index 4e44a2857..bd3366c93 100644 --- a/web/console/helpers/index.ts +++ b/web/console/helpers/index.ts @@ -38,3 +38,4 @@ export { getCookie } from './cookieUtil'; export { reduceK8sQueryString, reduceK8sRestfulPath, reduceNs, parseQueryString, cutNsStartClusterId } from './urlUtil'; export * from './request'; export * from './format'; +export * from './csrf'; diff --git a/web/console/helpers/reduceNetwork.ts b/web/console/helpers/reduceNetwork.ts index 477216bfb..5aa2809fa 100644 --- a/web/console/helpers/reduceNetwork.ts +++ b/web/console/helpers/reduceNetwork.ts @@ -21,6 +21,8 @@ import { RequestParams, ResourceInfo } from '../src/modules/common/models'; import { changeForbiddentConfig } from '../index.tke'; import { parseQueryString } from './urlUtil'; import { getProjectName } from './appUtil'; +import Cookies from 'js-cookie'; +import { createCSRFHeader } from '@helper'; /** 是否展示没有权限的弹窗 */ export const Init_Forbiddent_Config = { @@ -154,7 +156,8 @@ export const reduceNetworkRequest = async ( {}, { 'X-Remote-Extra-RequestID': uuid(), - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...createCSRFHeader() }, userDefinedHeader ), diff --git a/web/console/package-lock.json b/web/console/package-lock.json index b620cf78d..1225b9bae 100644 --- a/web/console/package-lock.json +++ b/web/console/package-lock.json @@ -1834,6 +1834,13 @@ "lodash": "^4.17.21", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0" + }, + "dependencies": { + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + } } }, "ahooks-v3-count": { @@ -7402,9 +7409,9 @@ "integrity": "sha512-wVdUBYQeY2gY73RIlPrysvpYx+2vheGo8Y1SNQv/BzHToWpAZzJU7Z6uheKMAe+GLSBig5/Ps2nxg/8tRB73xg==" }, "js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==" }, "js-tokens": { "version": "4.0.0", @@ -10761,6 +10768,11 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, + "spark-md5": { + "version": "3.0.2", + "resolved": "http://r.tnpm.oa.com/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + }, "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", diff --git a/web/console/package.json b/web/console/package.json index 7e500d63a..ca17ba30a 100644 --- a/web/console/package.json +++ b/web/console/package.json @@ -47,6 +47,7 @@ "ifvisible.js": "^1.0.6", "immer": "^7.0.8", "js-base64": "^3.4.5", + "js-cookie": "^3.0.1", "js-yaml": "^3.12.0", "lodash": "^4.17.14", "marked": "^0.7.0", @@ -67,6 +68,8 @@ "redux": "^4.0.1", "redux-logger": "^3.0.6", "redux-thunk": "=2.0.1", + "spark-md5": "^3.0.2", + "tea-chart": "^2.4.6", "tea-component": "^2.7.3", "ts-optchain": "^0.1.7", "use-immer": "^0.4.1", @@ -128,4 +131,4 @@ "webpack-cli": "^4.7.0", "webpack-dev-server": "^3.11.2" } -} +} \ No newline at end of file diff --git a/web/console/src/webApi/request.ts b/web/console/src/webApi/request.ts index 865e9c346..cfe3b4855 100644 --- a/web/console/src/webApi/request.ts +++ b/web/console/src/webApi/request.ts @@ -18,6 +18,8 @@ import { changeForbiddentConfig } from '@/index.tke'; import Axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; +import Cookies from 'js-cookie'; +import { createCSRFHeader } from '@helper'; const instance = Axios.create({ timeout: 10000 @@ -28,7 +30,8 @@ const instance = Axios.create({ instance.interceptors.request.use( config => { Object.assign(config.headers, { - 'X-Remote-Extra-RequestID': uuidv4() + 'X-Remote-Extra-RequestID': uuidv4(), + ...createCSRFHeader() }); return config; }, diff --git a/web/console/webpack/csrf.js b/web/console/webpack/csrf.js new file mode 100644 index 000000000..42f929ad6 --- /dev/null +++ b/web/console/webpack/csrf.js @@ -0,0 +1,28 @@ +const SparkMD5 = require('spark-md5'); + +function parseCookie(cookieStr) { + cookieStr.split('; ').reduce((all, item) => { + const [key, value] = item.split('='); + + return { + ...all, + [key]: value + }; + }, {}); +} + +function createCSRFHeader(cookieStr) { + const cookie = parseCookie(cookieStr); + + const tkeCookie = cookie?.['tke'] ?? ''; + + const token = SparkMD5.hash(tkeCookie); + + return { + 'X-CSRF-TOKEN': token + }; +} + +module.exports = { + createCSRFHeader +}; diff --git a/web/console/webpack/webpack.dev.js b/web/console/webpack/webpack.dev.js index feb2f5789..aa3ecfc00 100644 --- a/web/console/webpack/webpack.dev.js +++ b/web/console/webpack/webpack.dev.js @@ -20,6 +20,7 @@ const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin(); const path = require('path'); const { Host, Cookie } = require('../server.config'); +const { createCSRFHeader } = require('./csrf'); module.exports = ({ version }) => smp.wrap({ @@ -37,7 +38,7 @@ module.exports = ({ version }) => target: Host, secure: false, changeOrigin: true, - headers: { Cookie } + headers: { Cookie, ...createCSRFHeader(Cookie) } }, '/websocket': {