Turn on OpenBeken web auth and manage scripts over HTTP
Contents
Two shell scripts switch on an OpenBeken plug's web password over HTTP without touching its Wi-Fi settings, prove that requests without the password get 401, then upload .bat scripts to the plug's filesystem and restart it, with the password kept out of every command line.
- Tested on
- RZTK C3680W plug (OEM FrankEver FK-PW802EC-F, Tuya WB2S module, BK7231T) running OpenBeken 1.18.318 (OpenBK7231T build); client Fedora 44, curl 8.18.0, bash 5.3.9 (2026-10-16).
- Needs
- an OpenBeken plug on your LAN with web auth still off;
bashandcurlon the client; the list of everything that polls the plug (Home Assistant, a monitor, a script). - Time
- 15 min.
- Risk
- pollers without the password start getting 401 the moment auth goes on. The restart in the upload step drops the relay for a few seconds, so whatever is plugged into it loses power briefly. A lost password locks you out of the web UI and API. Undo is one request, but it was not exercised (see Undo).
What this protects and what it does not: OpenBeken's auth is HTTP Basic over plain HTTP, and the enable request carries the new password in a GET query string. It keeps a curious device on the LAN away from the relay, the firmware update endpoint and the flash reader. It does not stop anyone who can capture the plug's traffic.
Before you start#
- Placeholders, used everywhere below:
<plug>: the plug's IP address or DNS name.<web-password>: the new web password. Letters and digits only, 5 to 32 characters (see Notes for why).
- The firmware has web auth compiled in. The Wi-Fi configuration page (Config → Configure WiFi) shows an "Enable web authentication" checkbox and an "Admin Password" field. In 1.18.318 the feature is on at build time (
#define ALLOW_WEB_PASSWORD 1,src/new_pins.h:1732) and off at runtime until a password is set: an empty password skips the check (src/httpserver/http_basic_auth.c:12-14). - Relay start state. The upload step restarts the plug. If you need the relay back on after a restart, set it first with
/cm?cmnd=SetStartValue%201%201; my plug has that setting, which is why the relay channel reads 1 in Verify. - File layout. The scripts read the password from
../router/secrets.env, relative to their own directory. Keep this shape or edit the.line in both scripts:<dir>/plug/enable-auth.sh <dir>/plug/upload-scripts.sh <dir>/plug/led.bat, prot.bat, autoexec.bat <dir>/router/secrets.envrouter/secrets.envholds one line,OBSERVER_PLUG_PASSWORD=<web-password>. Make it mode 600 and keep it out of git. The.batfiles are the ones you want on the plug; mine are in the public repo listed under Sources. - Backup. Save a copy of every script already on the plug before you overwrite it:
GET http://<plug>/api/lfs/<name>returns the file; while auth is off it needs no password.
Steps#
Give every client that polls the plug the password, before anything else. The password is set in memory at once and saved to flash shortly after, so auth applies from the next request. Any poller still sending anonymous requests gets 401 from then on. The username is fixed:
admin(http_basic_auth.c:29). In my setup the poller is nut-observer, which readsOBSERVER_PLUG_PASSWORDfrom its environment and sends it with its voltage polls and LED commands. I updated it first. While auth is still off, a request that carries a password is accepted like any other, so clients can be switched over early.Save
plug/enable-auth.sh.#!/bin/bash # Turn on OpenBeken web authentication (HTTP Basic, user admin) with OBSERVER_PLUG_PASSWORD from # router/secrets.env. Run AFTER router/update-observer.sh, so the observer already sends the password. # Uses /cfg_wifi_set with only the password fields: Wi-Fi settings untouched, no module reset. # Undo: same form with web_admin_password_enabled absent (clears the password), sent with the password. set -euo pipefail cd "$(dirname "$0")" IP=${IP:-<plug>} set -a; . ../router/secrets.env; set +a [ -n "${OBSERVER_PLUG_PASSWORD:-}" ] || { echo "ABORT: OBSERVER_PLUG_PASSWORD missing (run router/update-observer.sh first)"; exit 1; } # URL with the password goes through curl's stdin config, not argv printf 'url = "http://%s/cfg_wifi_set?web_admin_password_enabled=1&web_admin_password=%s"\n' "$IP" "$OBSERVER_PLUG_PASSWORD" \ | curl -fsS --max-time 10 -K - | /usr/bin/grep -o 'Web password has been changed\|No changes detected' || true sleep 2 code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "http://$IP/cm?cmnd=STATUS%208") echo "without password: HTTP $code (want 401)" code=$(printf 'user = "admin:%s"\n' "$OBSERVER_PLUG_PASSWORD" | curl -s -o /dev/null -w '%{http_code}' --max-time 10 -K - "http://$IP/cm?cmnd=STATUS%208") echo "with password: HTTP $code (want 200)"Replace
<plug>on theIP=line, or leave it and run withIP=<address>in front.router/update-observer.shin the comments is my step 1; yours is whatever you did there.Why it is safe for the Wi-Fi settings: the request sends only
web_admin_password_enabledandweb_admin_password.http_fn_cfg_wifi_setchanges the SSID and Wi-Fi password only when thessid,pass,ssid2orpass2arguments are present (src/httpserver/http_fns.c:1889-1903). It schedules a module reset only when one of those changed (http_fns.c:1920-1926). The web password itself is set without a reset (http_fns.c:1905-1913).Why the password stays out of argv:
printfis a bash builtin, and curl reads the URL and theuser =line as a config file from stdin (-K -). Neither the password nor the URL that contains it appears in any process's command line.Run it.
$ ./plug/enable-auth.sh Web password has been changed No changes detected without password: HTTP 401 (want 401) with password: HTTP 200 (want 200)Expected: all four lines as above.
No changes detectedis the Wi-Fi half of the reply page (WiFi: No changes detected.,http_fns.c:1921): Wi-Fi settings unchanged, no reset. If the first line is missing, read curl's error above the output; a password under 5 characters is refused and prints onlyNo changes detected(Notes). Ifwithout passwordsays 200, auth is not on.Save
plug/upload-scripts.sh.#!/bin/bash # Upload led.bat, prot.bat and autoexec.bat to the plug's filesystem, then restart it so they run. # The AC180's wall input runs through this plug: the restart drops the relay for a few seconds. # Sends the web password from router/secrets.env (OBSERVER_PLUG_PASSWORD) if set; harmless while auth is off. set -euo pipefail cd "$(dirname "$0")" IP=${IP:-<plug>} set -a; . ../router/secrets.env; set +a AUTH=() [ -n "${OBSERVER_PLUG_PASSWORD:-}" ] && AUTH=(-K -) # credentials via stdin, not argv cr(){ if [ ${#AUTH[@]} -gt 0 ]; then printf 'user = "admin:%s"\n' "$OBSERVER_PLUG_PASSWORD" | curl -fsS --max-time 10 "${AUTH[@]}" "$@"; else curl -fsS --max-time 10 "$@"; fi; } for f in led.bat prot.bat autoexec.bat; do cr -X POST --data-binary @"$f" "http://$IP/api/lfs/$f" -o /dev/null && echo "uploaded $f" done cr "http://$IP/cm?cmnd=restart" -o /dev/null || true echo "restarting; waiting for the plug" for _ in $(seq 1 30); do sleep 3 if out=$(cr "http://$IP/cm?cmnd=STATUS%208" 2>/dev/null); then echo "$out"; exit 0; fi done echo "plug not back after 90 s"; exit 1Edit the file list in the
forline to match your scripts.POST /api/lfs/<name>writes the file (src/httpserver/rest_interface.c:283). The whole/api/tree is registered withauth_required = 1(rest_interface.c:79-80), so uploads need the password.Warn whatever hangs off the relay, then run it. The restart drops the relay for a few seconds. On my plug that cuts the power station's wall input, so the station briefly runs on battery.
$ ./plug/upload-scripts.sh uploaded led.bat uploaded prot.bat uploaded autoexec.bat restarting; waiting for the plug {"StatusSNS":{"Time":"2026-10-16T20:39:44","ENERGY":{"Power": 381.000000,"ApparentPower": 454.151275,"ReactivePower": 247.168716,"Factor":0.838927,"Voltage":232.121780,"Current":1.956521,"Frequency":0.000000,"ConsumptionTotal":5332.825683,"Yesterday": 0.047083,"ConsumptionLastHour":0.000000},"ESP32":{"Temperature": 49.9},"TempUnit":"C"}}Expected: one
uploadedline per file, then oneSTATUS 8JSON line once the plug answers again with the password.plug not back after 90 smeans it did not answer with the password within 90 s.
Verify#
Run from <dir> once the restart has finished. The scripts on the plug match the local copies, the plug has just rebooted, the relay is on (channel 1), the LED mode is 1 (channel 6, my led.bat), and the protection script has its 170 V under-voltage threshold and no trip (channels 11 and 12, my prot.bat):
$ set -a; . router/secrets.env; set +a; A(){ printf 'user = "admin:%s"\n' "$OBSERVER_PLUG_PASSWORD" | curl -fsS --max-time 10 -K - "$@"; }; for f in led.bat prot.bat; do A http://<plug>/api/lfs/$f | cmp -s - plug/$f && echo "$f same" || echo "$f DIFF"; done; A 'http://<plug>/cm?cmnd=STATUS%2011' | python3 -c 'import json,sys;print("uptime",json.load(sys.stdin).get("StatusSTS",{}).get("UptimeSec"))'; for c in 1 6 11 12; do printf "ch$c="; A "http://<plug>/cm?cmnd=GetChannel%20$c"; echo; done
led.bat same
prot.bat same
uptime 19
ch1=1
ch6=1
ch11=170
ch12=0
And without the password, both the command API and the filesystem API refuse, with the realm from http_basic_auth.c:50:
$ curl -s -D - -o /dev/null --max-time 10 "http://<plug>/cm?cmnd=STATUS%208"
HTTP/1.1 401 Unauthorized
Connection: close
WWW-Authenticate: Basic realm="OpenBeken HTTP Server"
$ curl -s -o /dev/null -w '%{http_code}\n' --max-time 10 "http://<plug>/api/lfs/led.bat"
401
Undo / rollback#
[UNVERIFIED: rollback not exercised] From the source: a /cfg_wifi_set request that has no web_admin_password_enabled argument clears the web password (http_fns.c:1915-1917). The request itself must carry the password, because with auth on /cfg_wifi_set is served only after the catch-all check (src/httpserver/new_http.c:938-942, handler at new_http.c:998). With no Wi-Fi arguments it does not reset the module, as in step 2. From <dir>/plug:
set -a; . ../router/secrets.env; set +a
printf 'url = "http://%s/cfg_wifi_set"\nuser = "admin:%s"\n' "<plug>" "$OBSERVER_PLUG_PASSWORD" | curl -fsS --max-time 10 -K -
Sending web_admin_password_enabled=0 does not clear it; only leaving the argument out does (http_fns.c:1905-1917). Then check curl -s -o /dev/null -w '%{http_code}\n' "http://<plug>/cm?cmnd=STATUS%208" prints 200. Uploaded scripts are undone by uploading the backup copies from Before you start with upload-scripts.sh.
Failure modes#
| Symptom (exact message) | Cause | Fix |
|---|---|---|
A poller's requests fail with 401 after step 3 (without password: HTTP 401 is the same response) | The client still sends no credentials, or not as user admin | Give it the password with HTTP Basic user admin (step 1). Nothing to change on the plug. |
A poller's request fails during step 5; my monitor logged set mode 1 failed: Get "http://<plug>:80/cm?cmnd=SetChannel%20… (the error tail wasn't captured) | The plug is rebooting and not answering | None needed. My monitor's next LED command, about 8 s later, went through. |
Field notes#
- 2026-10-16 21:05 UTC, same plug, OpenBeken 1.18.318, Fedora 44 client, curl 8.18.0: re-checked from the client with no password.
STATUS 8andGET /api/lfs/led.batboth returned 401 with the OpenBeken realm (pasted in Verify). Auth had stayed on through the 20:39 restart. - 2026-10-16 20:39 UTC, same plug and client: ran
upload-scripts.shwith auth on, to install new LED patterns. The three uploads went through with the password, the plug answered again, and the check right after showed uptime 19 s,led.batandprot.batbyte-identical to my copies, relay on, no trip.autoexec.batwas unchanged and I did not compare it. - 2026-10-16 10:56 UTC, same plug and client: ran
enable-auth.sh. Output as in step 3. I read the monitor's log twice over the next 1 min 40 s: no failed voltage polls and no failed LED sends, so step 1 had worked. - 2026-10-16 10:38 UTC, same plug and client, auth still off: first run of
upload-scripts.sh, after giving the monitor the password (about 10:23 UTC). The script sent the password anyway; with no password set, the plug accepted the requests like anonymous ones. The plug was back with uptime 42 s at the check;led.batandprot.batmatched. During the restart one LED command from the monitor failed (logged asset mode 1 failed: Get …, error tail not captured), and the retry at 10:38:00 got through. The monitor never reported the power station on battery: it needs two on-battery readings 20 s apart, and the relay was open for less than that. - 2026-10-16, morning, before any of this: my notes and a draft of the plug post said OpenBeken had no web login. The 1.18.318 source (
http_basic_auth.c,http_fns.c) showed one: off by default, with its checkbox on the Wi-Fi configuration page.
Notes#
- Password characters.
enable-auth.shputs the password unencoded into a URL query string, and both scripts put it inside a double-quoted curl config value.&,#,%, space,"or\break one or the other. Use letters and digits. OpenBeken rejects passwords under 5 characters withWeb password needs to be at least 5 characters long!(http_fns.c:1908-1909, from source, not hit). It stores at most 32 (char webPassword[33],new_pins.h:1745) and compares 32 (http_basic_auth.c:31). - The web UI path also works, but it is the same Wi-Fi form, and it submits SSID and Wi-Fi password along with the auth fields (
http_fns.c:1805-1829). The script exists to send only the two auth fields. - Any
/cfg_wifi_setwithoutweb_admin_password_enabledclears the password, including a script or bookmark that sets only SSID and Wi-Fi password (http_fns.c:1915-1917). - Logged-in users can read the password: the Wi-Fi page pre-fills the Admin Password field with the stored value (
http_fns.c:1825). - Not covered by auth: commands received over MQTT, if MQTT is configured (
new_mqtt.c:826,844,854); they never pass through HTTP auth. Also endpoints registered by optional drivers withauth_required = 0, for example SSDP's/ssdp.xmland/obkdevicelist, and the WeMo and Hue emulation pages (src/driver/drv_ssdp.c:438,445,drv_wemo.c:316-319,drv_hue.c:253). They answer only when that driver is running. I did not check which drivers run on mine. - Safe mode skips auth only if flag 44 (
OBK_FLAG_HTTP_DISABLE_AUTH_IN_SAFE_MODE,new_pins.h:1419) was set beforehand (http_basic_auth.c:12). Without it, the recovery paths the source shows are: theclearConfig/clearAllcommands (cmd_main.c:1116,1126), which reset the whole config including the password and the Wi-Fi settings (new_cfg.c:110), sent over MQTT if MQTT is configured, or a serial reflash. None of them tested. - Next layer, not done here: a separate IoT network that only the router and the monitor can reach.
- The story of this plug (reflash, pin map, LED and protection scripts): The pin template was for a different plug.
Filed under: openbeken, tuya, smart-home, security, curl
Sources#
- OpenBK7231T_App at tag 1.18.318 — OpenBeken source; every
file:lineabove is from this tag (thehttpserver/and core files cited here were fetched from GitHub and compared byte for byte with my local checkout; thedriver/lines were read in the checkout only).src/httpserver/http_basic_auth.c— the check: empty password = open, useradmin, 32-character compare, 401 with the realm.src/httpserver/new_http.c— per-callbackauth_requiredcheck (l. 929) and the catch-all check (l. 938-942) in front of/cfg_wifi_setand/cm.src/httpserver/rest_interface.c—/api/registered with auth (l. 79-80); LFS upload (l. 283).src/httpserver/http_fns.c—http_fn_cfg_wifi_set(l. 1874-1932) and the Wi-Fi form (l. 1805-1829).src/new_pins.h—ALLOW_WEB_PASSWORD,webPassword[33], flag 44.
- openbeken-grid-canary-plug — my plug's
led.bat,prot.bat,autoexec.bat, pin map and template (the two shell scripts above are not in it).