flawopen.com/sql-injection/Java
Java SQL Injection: JDBC, JPA और MyBatis
Java SQL Injection तब होती है जब input query में जोड़ा जाता है। मानों को bind करें और ORDER BY जैसे identifiers को allowlist से सीमित करें。[1], [2], [3], [7], [4], [6]
ऐसी फैक्ट्री सोचिए जो ग्राहकों के लेबल आने से पहले धातु का साँचा लॉक कर देती है। JDBC PreparedStatement पहले ? वाला स्लॉट-साँचा बनाता है; फिर setLong(1, userId) निर्देश बदले बिना मान को स्लॉट में रखता है। तैयारी से पहले text concatenate करने पर वह साँचे में खुद जाता है। JdbcTemplate और JPQL setParameter भी मान अलग रखते हैं। MyBatis का अंतर स्पष्ट है: #{status} bound value है, जबकि ${status} जोड़ा गया SQL text है। ORDER BY fields identifiers हैं, values नहीं; उन्हें allowlist या स्वीकृत Sort.by property से map करें।[2], [3], [7], [4], [8]
इस पेज के मुख्य शब्द
PreparedStatement?मार्कर वाले SQL से, कोई भी मान मिलने से पहले, कंपाइल किया गया JDBC स्टेटमेंट। मानsetLong(1, id)जैसे टाइप्ड setter से आते हैं और क्वेरी को कभी नहीं बदल सकते।prepareStatement("..." + id)हमलावर का टेक्स्ट कंपाइल कर देता है और कुछ नहीं बचाता।[2], [1]JdbcTemplate query(sql, args)- Spring के
queryForList(sql, Long.class, status)औरupdate(sql, args...)आख़िरी आर्ग्युमेंट को PreparedStatement के ज़रिए?मार्कर से बाँधते हैं।NamedParameterJdbcTemplateयही काम:nameमार्कर से करता है।sqlआर्ग्युमेंट में स्ट्रिंग जोड़ना यह सब बेकार कर देता है।[3], [2] JPQL / HQL injection- JPA और Hibernate की क्वेरी भी स्ट्रिंग से पार्स होती हैं।
createQuery("... = '" + email + "'")इनपुट को JPQL बदलने देता है, जिसे Hibernate फिर SQL में बदलता है।:emailके साथsetParameterया Criteria API इस्तेमाल करें।[7], [1] MyBatis #{} vs ${}- MyBatis मैपर में
#{status}एक बाइंड किया हुआ?पैरामीटर बनता है।${status}SQL में टेक्स्ट की तरह चिपका दिया जाता है।${}सिर्फ़ उन मानों के लिए सुरक्षित है जिन्हें आपने ख़ुद चुना है, जैसे अनुमति-सूची का कोई कॉलम।[4] Spring Data Sort- Spring Data
Sort.by("email")को मैप की गई प्रॉपर्टी पर ORDER BY में बदलता है और अनजान नाम ठुकरा देता है, इसलिए हाथ से ORDER BY बनाने के बजाय इसे चुनें। फिर भी रिक्वेस्ट की कुंजियों को अनुमति-सूची से मिलाएँ, ताकि कोई उन फ़ील्ड पर सॉर्ट न कर सके जिन्हें आप दिखाना नहीं चाहते थे।[8]
फ्रेमवर्क मैपिंग
- इस गाइड में बताई गई कमजोरी के लिए CWE संदर्भ: CWE-89
- OWASP Top 10:2025 — A05:2025 Injection
हमले का चरण-दर-चरण प्रवाह
रिक्वेस्ट का मान रिपॉज़िटरी तक पहुँचता है
Spring का @GetMapping हैंडलर @RequestParam String id लेता है और उसे findEmail() को देता है। घोषित प्रकार String होने के कारण Spring मान को किसी दूसरे प्रकार में नहीं बदलता; उसके प्रारूप पर भरोसा करने से पहले स्पष्ट रूप से जाँचें।[9]
सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन
// UserRepository.java: नीचे की हर क्वेरी रिक्वेस्ट डेटा को क्वेरी टेक्स्ट में जोड़ती है
public class UserRepository {
private final DataSource dataSource;
private final JdbcTemplate jdbc;
private final EntityManager em;
public UserRepository(DataSource dataSource, JdbcTemplate jdbc, EntityManager em) {
this.dataSource = dataSource;
this.jdbc = jdbc;
this.em = em;
}
// JDBC: Statement जोड़ी गई स्ट्रिंग को मान समेत पार्स करता है
public String findEmail(String userId) throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT email FROM users WHERE id = " + userId)) {
return rs.next() ? rs.getString(1) : null;
}
}
// JdbcTemplate: Spring के SQL देखने से पहले ही स्ट्रिंग जुड़ चुकी है
public List<Long> findOrderIds(String status) {
return jdbc.queryForList("SELECT id FROM orders WHERE status = '" + status + "'", Long.class);
}
// JPA: JPQL ख़ुद इनपुट से बनता है, फिर SQL में बदलता है
public List<User> findByEmail(String email) {
return em.createQuery("SELECT u FROM User u WHERE u.email = '" + email + "'", User.class)
.getResultList();
}
// ORDER BY फ़ील्ड सीधे रिक्वेस्ट से
public List<User> listUsers(String sortField) {
return em.createQuery("SELECT u FROM User u ORDER BY u." + sortField, User.class)
.getResultList();
}
}
// UserRepository.java: मान बाइंड पैरामीटर हैं, सॉर्ट फ़ील्ड अनुमति-सूची से आते हैं
public class UserRepository {
private static final Map<String, String> SORTED_QUERIES = Map.of(
"id", "SELECT u FROM User u ORDER BY u.id",
"email", "SELECT u FROM User u ORDER BY u.email",
"created", "SELECT u FROM User u ORDER BY u.createdAt");
private final DataSource dataSource;
private final JdbcTemplate jdbc;
private final EntityManager em;
public UserRepository(DataSource dataSource, JdbcTemplate jdbc, EntityManager em) {
this.dataSource = dataSource;
this.jdbc = jdbc;
this.em = em;
}
// JDBC: पहले ? मार्कर कंपाइल, फिर टाइप्ड setter से मान
public String findEmail(long userId) throws SQLException {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT email FROM users WHERE id = ?")) {
ps.setLong(1, userId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getString(1) : null;
}
}
}
// JdbcTemplate: आख़िरी आर्ग्युमेंट ? मार्कर से बँधते हैं
public List<Long> findOrderIds(String status) {
return jdbc.queryForList("SELECT id FROM orders WHERE status = ?", Long.class, status);
}
// JPA: नामित पैरामीटर, मान setParameter से बँधता है
public List<User> findByEmail(String email) {
return em.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class)
.setParameter("email", email)
.getResultList();
}
// अनुमति-सूची से मिलान; क्वेरी में सिर्फ़ स्थिर JPQL टुकड़े जाते हैं
public List<User> listUsers(String sortKey) {
String jpql = SORTED_QUERIES.getOrDefault(sortKey, SORTED_QUERIES.get("id"));
return em.createQuery(jpql, User.class)
.getResultList();
}
}
इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट
- ✓SpotBugs के
SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTEऔर Find Security Bugs केSQL_INJECTION_JDBC,SQL_INJECTION_SPRING_JDBCवSQL_INJECTION_JPAपर बिल्ड फ़ेल करें।[10], [5] - ✓हर उस
Statement.execute*,createQuery,createNativeQueryया JdbcTemplate कॉल को अस्वीकार करें जिसका SQL आर्ग्युमेंट+,String.formatयाStringBuilderसे बना हो।[2], [3], [7] - ✓CI में MyBatis मैपर में
${खोजें; हर मिलान कोड में तय और मैपर में दर्ज अनुमति-सूची का मान होना चाहिए।[4] - ✓ORDER BY को
Sort.by()से या प्रॉपर्टी नामों कीMapअनुमति-सूची से बनाएँ — कभी सीधे@RequestParamसे नहीं।[8], [7] - ✓रिक्वेस्ट पैरामीटर को उसके वास्तविक डोमेन प्रकार (
longसंख्यात्मक ID के लिए याUUIDUUID ID के लिए) में घोषित करें, ताकि Spring रिपॉज़िटरी लॉजिक से पहले असफल रूपांतरण को अस्वीकार कर सके। SQL Injection रोकने का नियंत्रण SQL पैरामीटर बाइंडिंग ही है।[9], [2]
स्रोत
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- Oracle — Using Prepared Statements (JDBC tutorial)
- Spring Framework — JdbcTemplate
- MyBatis — Mapper XML: String Substitution
- Find Security Bugs — SQL_INJECTION rules
- OWASP SQL Injection Prevention Cheat Sheet
- Jakarta EE Tutorial — The Jakarta Persistence Query Language
- Spring Data JPA — Query Methods and Sorting
- Spring Framework — @RequestParam
- SpotBugs — SQL Injection Detectors