CVE-2022-4454: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability, which was classified as critical, has been found in m0ver bible-online. Affected by this issue is the function query of the file src/main/java/custom/application/search.java of the component Search Handler. The manipulation leads to sql injection. The name of the patch is 6ef0aabfb2d4ccd53fcaa9707781303af357410e. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-215444.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2022-12-13T18:15Z
Repository: https://github.com/m0ver/bible-online
Language: Java
Files changed: src/main/java/custom/application/search.java
Commit date: 2022-10-02 14:51:52 +0800
Commit message: Fixed the SQL Injection issue.

Diff: diff --git a/src/main/java/custom/application/search.java b/src/main/java/custom/application/search.java
index 89b351f..96b4d38 100755
--- a/src/main/java/custom/application/search.java
+++ b/src/main/java/custom/application/search.java
@@ -192,36 +192,28 @@ public class search extends AbstractApplication {
         } else {
             this.setVariable("keyword", "");
             return this;
         }
 
-        StringBuffer condition = new StringBuffer();
+        StringBuilder condition = new StringBuilder();
         int i = 0, j, k = 0;
-        String _keyword;
+        String[] _keywords = new String[keywords.length];
         while (i < keywords.length) {
-            _keyword = keywords[i];
-            if (_keyword.trim().length() > 0) {
-                if (condition.length() == 0) {
-                    condition.append(" bible.content like '%" + _keyword + "%' ");
-                } else {
-                    condition.append(" AND bible.content like '%" + _keyword + "%' ");
-                    /*
-                     * if(true)
-                     * condition.append(" AND bible.content like '%"+keywords[i]+"%' ");
-                     * else
-                     * condition.append(" OR bible.content like '%"+keywords[i]+"%' ");
-                     */
-                }
+            _keywords[i] = "%" + keywords[i] + "%";
+            if (condition.length() == 0) {
+                condition.append(" bible.content like ? ");
+            } else {
+                condition.append(" AND bible.content like ? ");
             }
             i++;
         }
 
         Locale locale = this.getLocale();
         if (condition.length() == 0)
-            condition.append(" book.language='" + locale + "' ");
+            condition.append(" book.language='").append(locale).append("' ");
         else
-            condition.append(" AND book.language='" + locale + "' ");
+            condition.append(" AND book.language='").append(locale).append("' ");
 
         book book = new book();
         bible bible = new bible();
         if (locale.toString().equalsIgnoreCase(Locale.US.toString())) {
             bible.setTableName("NIV");
@@ -238,11 +230,11 @@ public class search extends AbstractApplication {
                 + pageSize;
         String look = "SELECT count(bible.id) AS size FROM " + bible.getTableName()
                 + " as bible left join " + book.getTableName()
                 + " as book on bible.book_id=book.book_id where " + condition;
 
-        Table vtable = bible.find(SQL, new Object[]{});
+        Table vtable = bible.find(SQL, _keywords);
         boolean noResult = vtable.size() > 0;
 
         if (!noResult && query.length() > 0) {
             try {
                 Table list = book.findWith("WHERE language=? and book_name=?",
@@ -264,22 +256,22 @@ public class search extends AbstractApplication {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
 
-        Row found = bible.findOne(look, new Object[]{});
+        Row found = bible.findOne(look, _keywords);
 
         long startTime = System.currentTimeMillis();
         Pager pager = new Pager();
         pager.setPageSize(pageSize);
         pager.setCurrentPage(page);
         pager.setListSize(found.getFieldInfo("size").intValue());
 
         Field field;
         int next = pager.getStartIndex();// 此位置即为当前页的第一条记录的ID
 
-        html.append("<ol class=\"searchresults\" start=\"" + next + "\">\r\n");
+        html.append("<ol class=\"searchresults\" start=\"").append(next).append("\">\r\n");
 
         String finded, word;
         Row row;
         Enumeration<Row> table = vtable.elements();
         int n = 0;
@@ -347,11 +339,11 @@ public class search extends AbstractApplication {
         pager.setControlBarText(this.getProperty("page.controlbar.text"));
 
         html.append("<div class=\"pagination\" style=\"cursor:default\">"
                 + pager.getPageControlBar(actionURL) + "</div>\r\n");
         html.append("<!-- "
-                + String.valueOf(System.currentTimeMillis() - startTime) + " -->");
+                + (System.currentTimeMillis() - startTime) + " -->");
 
         int start = page - 1 == 0 ? 1 : (page - 1) * pageSize + 1, end = page
                 * pageSize;
 
         this.setVariable("start", String.valueOf(start));
@@ -379,11 +371,11 @@ public class search extends AbstractApplication {
 
         return this;
     }
 
     public String feed(String query) throws ApplicationException {
-        StringBuffer xml = new StringBuffer();
+        StringBuilder xml = new StringBuilder();
         String finded = "";
         String[] keywords;
         boolean noResult = true;
 
         int page = 1, pageSize = 20, startIndex = (page - 1) * pageSize;

Vulnerable code:         StringBuffer condition = new StringBuffer();
        String _keyword;
            _keyword = keywords[i];
            if (_keyword.trim().length() > 0) {
                if (condition.length() == 0) {
                    condition.append(" bible.content like '%" + _keyword + "%' ");
                } else {
                    condition.append(" AND bible.content like '%" + _keyword + "%' ");
                    /*
                     * if(true)
                     * condition.append(" AND bible.content like '%"+keywords[i]+"%' ");
                     * else
                     * condition.append(" OR bible.content like '%"+keywords[i]+"%' ");
                     */
                }
            condition.append(" book.language='" + locale + "' ");
            condition.append(" AND book.language='" + locale + "' ");
        Table vtable = bible.find(SQL, new Object[]{});
        Row found = bible.findOne(look, new Object[]{});
        html.append("<ol class=\"searchresults\" start=\"" + next + "\">\r\n");
                + String.valueOf(System.currentTimeMillis() - startTime) + " -->");
        StringBuffer xml = new StringBuffer();
Fixed code:         StringBuilder condition = new StringBuilder();
        String[] _keywords = new String[keywords.length];
            _keywords[i] = "%" + keywords[i] + "%";
            if (condition.length() == 0) {
                condition.append(" bible.content like ? ");
            } else {
                condition.append(" AND bible.content like ? ");
            condition.append(" book.language='").append(locale).append("' ");
            condition.append(" AND book.language='").append(locale).append("' ");
        Table vtable = bible.find(SQL, _keywords);
        Row found = bible.findOne(look, _keywords);
        html.append("<ol class=\"searchresults\" start=\"").append(next).append("\">\r\n");
                + (System.currentTimeMillis() - startTime) + " -->");
        StringBuilder xml = new StringBuilder();