CVE-2024-37896: Insufficient Information (NVD-CWE-noinfo)
value: Gin-vue-admin is a backstage management system based on vue and gin. Gin-vue-admin <= v2.6.5 has SQL injection vulnerability. The SQL injection vulnerabilities occur when a web application allows users to input data into SQL queries without sufficiently validating or sanitizing the input. Failing to properly enforce restrictions on user input could mean that even a basic form input field can be used to inject arbitrary and potentially dangerous SQL commands. This could lead to unauthorized access to the database, data leakage, data manipulation, or even complete compromise of the database server. This vulnerability has been addressed in commit `53d033821` which has been included in release version 2.6.6. Users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-06-17T20:15Z
Repository: https://github.com/flipped-aurora/gin-vue-admin
Language: JavaScript
Files changed: server/service/system/sys_export_template.go, web/vite.config.js, web/vitePlugin/secret/index.js
Commit date: 2024-06-02 21:13:51 +0800
Commit message: feature:修复已知安全问题。

Diff: diff --git a/server/service/system/sys_export_template.go b/server/service/system/sys_export_template.go
index c27bf466..f5334827 100644
--- a/server/service/system/sys_export_template.go
+++ b/server/service/system/sys_export_template.go
@@ -201,18 +201,47 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
 		if e == nil {
 			db = db.Offset(o)
 		}
 	}
 
+	// 获取当前表的所有字段
+	table := template.TableName
+	orderColumns, err := global.GVA_DB.Migrator().ColumnTypes(table)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// 创建一个 map 来存储字段名
+	fields := make(map[string]bool)
+
+	for _, column := range orderColumns {
+		fields[column.Name()] = true
+	}
+
 	// 通过参数传入order
 	order := values.Get("order")
-	if order != "" {
-		db = db.Order(order)
-	}
-	// 模板的默认order
+
 	if order == "" && template.Order != "" {
-		db = db.Order(template.Order)
+		// 如果没有order入参，这里会使用模板的默认排序
+		order = template.Order
+	}
+
+	if order != "" {
+		checkOrderArr := strings.Split(order, " ")
+		orderStr := ""
+		// 检查请求的排序字段是否在字段列表中
+		if _, ok := fields[checkOrderArr[0]]; !ok {
+			return nil, "", fmt.Errorf("order by %s is not in the fields", order)
+		}
+		orderStr = checkOrderArr[0]
+		if len(checkOrderArr) > 1 {
+			if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
+				return nil, "", fmt.Errorf("order by %s is not secure", order)
+			}
+			orderStr = orderStr + " " + checkOrderArr[1]
+		}
+		db = db.Order(orderStr)
 	}
 
 	err = db.Debug().Find(&tableMap).Error
 	if err != nil {
 		return nil, "", err
diff --git a/web/vite.config.js b/web/vite.config.js
index 1de5fc39..2ce4aa16 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -9,16 +9,18 @@ import * as dotenv from 'dotenv'
 import * as fs from 'fs'
 import vuePlugin from '@vitejs/plugin-vue'
 import GvaPosition from './vitePlugin/gvaPosition'
 import GvaPositionServer from './vitePlugin/codeServer'
 import fullImportPlugin from './vitePlugin/fullImport/fullImport.js'
-import { svgBuilder } from './vitePlugin/svgIcon/svgIcon.js'
+import { svgBuilder } from 'vite-auto-import-svg'
+import { AddSecret } from './vitePlugin/secret'
 // @see https://cn.vitejs.dev/config/
 export default ({
   command,
   mode
 }) => {
+  AddSecret("")
   const NODE_ENV = mode || 'development'
   const envFiles = [
     `.env.${NODE_ENV}`
   ]
   for (const file of envFiles) {
@@ -104,15 +106,15 @@ export default ({
     config.plugins.push(
       fullImportPlugin()
     )
   } else {
     config.plugins.push(AutoImport({
-      resolvers: [ElementPlusResolver()]
-    }),
-    Components({
-      resolvers: [ElementPlusResolver({
-        importStyle: 'sass'
-      })]
-    }))
+        resolvers: [ElementPlusResolver()]
+      }),
+      Components({
+        resolvers: [ElementPlusResolver({
+          importStyle: 'sass'
+        })]
+      }))
   }
   return config
 }
diff --git a/web/vitePlugin/secret/index.js b/web/vitePlugin/secret/index.js
new file mode 100644
index 00000000..56fd3333
--- /dev/null
+++ b/web/vitePlugin/secret/index.js
@@ -0,0 +1,6 @@
+export function AddSecret(secret) {
+  if(!secret){
+    secret = ""
+  }
+  global['gva-secret'] = secret;
+}
diff --git a/web/vitePlugin/svgIcon/svgIcon.js b/web/vitePlugin/svgIcon/svgIcon.js
deleted file mode 100644
index 42b04300..00000000
--- a/web/vitePlugin/svgIcon/svgIcon.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { readFileSync, readdirSync } from 'fs'
-const svgTitle = /<svg([^>+].*?)>/
-const clearHeightWidth = /(width|height)="([^>+].*?)"/g
-const hasViewBox = /(viewBox="[^>+].*?")/g
-const clearReturn = /(\r)|(\n)/g
-function findSvgFile(dir) {
-  const svgRes = []
-  const dirents = readdirSync(dir, {
-    withFileTypes: true
-  })
-  for (const dirent of dirents) {
-    if (dirent.isDirectory()) {
-      svgRes.push(...findSvgFile(dir + dirent.name + '/'))
-    } else {
-      const svg = readFileSync(dir + dirent.name)
-        .toString()
-        .replace(clearReturn, '')
-        .replace(svgTitle, ($1, $2) => {
-          let width = 0
-          let height = 0
-          let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {
-            if (s2 === 'width') {
-              width = s3
-            } else if (s2 === 'height') {
-              height = s3
-            }
-            return ''
-          })
-          if (!hasViewBox.test($2)) {
-            content += `viewBox="0 0 ${width} ${height}"`
-          }
-          return `<symbol id="${dirent.name.replace('.svg', '')}" ${content}>`
-        })
-        .replace('</svg>', '</symbol>')
-      svgRes.push(svg)
-    }
-  }
-  return svgRes
-}
-export const svgBuilder = (path) => {
-  if (path === '') return
-  const res = findSvgFile(path)
-  const timestamp = Date.now()
-  const secretCode = '087AC4D233B64EB0'
-  return {
-    name: 'svg-transform',
-    transformIndexHtml(html) {
-      return html.replace(
-        '<head>',
-        `
-          <head>
-            <meta name="keywords" content="${timestamp},${secretCode}">
-        `
-      ).replace(
-        '<body>',
-        `
-          <body>
-            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
-              ${res.join('')}
-            </svg>
-        `
-      )
-    }
-  }
-}

Vulnerable code: 	if order != "" {
		db = db.Order(order)
	}
	// 模板的默认order
		db = db.Order(template.Order)
import { svgBuilder } from './vitePlugin/svgIcon/svgIcon.js'
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver({
        importStyle: 'sass'
      })]
    }))
import { readFileSync, readdirSync } from 'fs'
const svgTitle = /<svg([^>+].*?)>/
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
const hasViewBox = /(viewBox="[^>+].*?")/g
const clearReturn = /(\r)|(\n)/g
function findSvgFile(dir) {
  const svgRes = []
  const dirents = readdirSync(dir, {
    withFileTypes: true
  })
  for (const dirent of dirents) {
    if (dirent.isDirectory()) {
      svgRes.push(...findSvgFile(dir + dirent.name + '/'))
    } else {
      const svg = readFileSync(dir + dirent.name)
        .toString()
        .replace(clearReturn, '')
        .replace(svgTitle, ($1, $2) => {
          let width = 0
          let height = 0
          let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {
            if (s2 === 'width') {
              width = s3
            } else if (s2 === 'height') {
              height = s3
            }
            return ''
          })
          if (!hasViewBox.test($2)) {
            content += `viewBox="0 0 ${width} ${height}"`
          }
          return `<symbol id="${dirent.name.replace('.svg', '')}" ${content}>`
        })
        .replace('</svg>', '</symbol>')
      svgRes.push(svg)
    }
  }
  return svgRes
}
export const svgBuilder = (path) => {
  if (path === '') return
  const res = findSvgFile(path)
  const timestamp = Date.now()
  const secretCode = '087AC4D233B64EB0'
  return {
    name: 'svg-transform',
    transformIndexHtml(html) {
      return html.replace(
        '<head>',
        `
          <head>
            <meta name="keywords" content="${timestamp},${secretCode}">
        `
      ).replace(
        '<body>',
        `
          <body>
            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
              ${res.join('')}
            </svg>
        `
      )
    }
  }
}
Fixed code: 	// 获取当前表的所有字段
	table := template.TableName
	orderColumns, err := global.GVA_DB.Migrator().ColumnTypes(table)
	if err != nil {
		return nil, "", err
	}

	// 创建一个 map 来存储字段名
	fields := make(map[string]bool)

	for _, column := range orderColumns {
		fields[column.Name()] = true
	}


		// 如果没有order入参，这里会使用模板的默认排序
		order = template.Order
	}

	if order != "" {
		checkOrderArr := strings.Split(order, " ")
		orderStr := ""
		// 检查请求的排序字段是否在字段列表中
		if _, ok := fields[checkOrderArr[0]]; !ok {
			return nil, "", fmt.Errorf("order by %s is not in the fields", order)
		}
		orderStr = checkOrderArr[0]
		if len(checkOrderArr) > 1 {
			if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
				return nil, "", fmt.Errorf("order by %s is not secure", order)
			}
			orderStr = orderStr + " " + checkOrderArr[1]
		}
		db = db.Order(orderStr)
import { svgBuilder } from 'vite-auto-import-svg'
import { AddSecret } from './vitePlugin/secret'
  AddSecret("")
        resolvers: [ElementPlusResolver()]
      }),
      Components({
        resolvers: [ElementPlusResolver({
          importStyle: 'sass'
        })]
      }))
export function AddSecret(secret) {
  if(!secret){
    secret = ""
  }
  global['gva-secret'] = secret;
}