Consumer IP cameras are usually sold as cloud-connected appliances.
You install the vendor app, create an account, pair the device, and from that point on the camera becomes part of someone else's ecosystem.
I wanted to answer a simple question:
Can a low-cost Tenda CP3 camera be used locally without relying on the vendor cloud?
The answer is yes.
The Tenda CP3 exposes local RTSP and ONVIF services that allow:
- local video streaming
- media profile discovery
- PTZ control
- device information retrieval
- security behavior validation
In practice, this camera is not technically cloud-only.
It is a capable embedded Linux device with a constrained consumer interface layered on top.
DemoThe demo shows a fully local control workflow:
- RTSP live video
- ONVIF PTZ control
- keyboard-based local control
- no vendor cloud
- no official mobile app
Demo video:
Project GoalThe goal of this project was not to exploit or bypass anything.
The objective was to validate whether a personally owned Tenda CP3 camera could be integrated into a local, self-hosted environment using standard protocols.
The target architecture is:
This allows the camera to be used in a homelab or private monitoring setup without exposing it directly to the Internet.
Hardware TestedThe tested device was:
Manufacturer: Tenda
Model: CP3V3.0
FirmwareVersion: V31.1.9.63
HardwareId: V1.0
This information was retrieved directly from the device using ONVIF.
Network DiscoveryThe first step was to scan the camera on the local network.
A full TCP scan showed several exposed services:
22/tcp SSH Dropbear
23/tcp Telnet BusyBox
80/tcp HTTP
554/tcp RTSP
8000/tcp ONVIF / SOAP endpoint
This was already interesting.
For a low-cost consumer camera, this is a fairly broad local exposure surface.
The important services for this project were:
- 554/tcp for RTSP video
- ONVIF SOAP endpoints for device management, media discovery, and PTZ control
The camera exposes working RTSP streams.
Using ONVIF media discovery, the following profiles were identified:
ProfileToken_MainStream
ProfileToken_SubStream
They map to:ProfileToken_MainStream -> rtsp://<CAMERA_IP>:554/ch=1?subtype=0
ProfileToken_SubStream -> rtsp://<CAMERA_IP>:554/ch=1?subtype=1The main stream provides the highest quality.
The substream is lower resolution and better suited for interactive control because it has lower latency and lower decoding cost.
A simple validation with `ffprobe` confirms the stream:
ffprobe -rtsp_transport tcp \
"rtsp://admin:admin123456@<CAMERA_IP>:554/ch=1?subtype=0"The stream characteristics were:
RTSP Server: ZNRTSPServer
Video Codec: HEVC / H.265
Resolution: 2304x1296
Frame Rate: 20 FPS
Audio Codec: PCM A-law
Audio Rate: 8000 Hz mono
This confirms that the device provides a real local video stream without using the vendor cloud.
ONVIF DiscoveryThe camera exposes several ONVIF services:
- Device Management
- Media Service
- PTZ Service
- Imaging Service
- Event Service
- Analytics Service
The ONVIF Device Management service was used to retrieve device information and capabilities.
The Media service was used to retrieve available profiles and RTSP URIs.
The PTZ service was used to move the camera locally.
This was implemented using Python and onvif-zeep.
Local PTZ ControlOnce the media profiles were discovered, the PTZ service could be used to move the camera.
Validated movement:
- pan left
- pan right
- tilt up
- tilt down
The implementation uses ONVIF `ContinuousMove` followed by `Stop`.
For manual control, I created a keyboard-based PTZ controller:
The controls are:
Arrow Up = tilt up
Arrow Down = tilt down
Arrow Left = pan left
Arrow Right = pan right
q = quit
Instead of moving indefinitely, each key press performs a short movement step and then stops automatically.This makes the camera much easier to control precisely
Interactive Demo ModeTo make the project easy to demonstrate, I created a single script:
./scripts/demo-local-control.sh <CAMERA_IP>The script does two things:
1. starts low-latency RTSP playback in the current shell
2. opens a second terminal with the keyboard PTZ controller
This creates a simple local operator console.
The video stream is handled by `ffplay`.
The PTZ controller is handled by Python and ONVIF.
Low-Latency RTSP PlaybackDefault RTSP playback introduced too much latency for comfortable PTZ control.
After testing several combinations, the best practical configuration was:
ffplay \
-rtsp_transport tcp \
-fflags nobuffer \
-flags low_delay \
-framedrop \
-probesize 32 \
-analyzeduration 0 \
-max_delay 0 \
-avioflags direct \
-sync audio \
"rtsp://admin:admin123456@<CAMERA_IP>:554/ch=1?subtype=1The key detail is this:
-sync audioKeeping audio enabled improved perceived latency.
This is counterintuitive, but reproducible.
ffplay uses the audio stream as the synchronization clock. With audio enabled, the video feedback during PTZ control became much more responsive.
Without audio, the video-only stream tended to buffer more and the camera felt sluggish during movement.
For interactive control, the substream is recommended:
subtype=1For observation or recording, the main stream is better:
subtype=0
Device Connection SensitivityOne important behavior emerged during testing.
The camera appears sensitive to simultaneous protocol initialization.
If RTSP playback and ONVIF PTZ connection are opened at exactly the same time, ONVIF may fail with errors such as:
Connection aborted
BadStatusLine
This suggests that the embedded firmware struggles with concurrent session startup.
The demo script mitigates this by sequencing the startup:
1. start RTSP playback first
2. wait a few seconds
3. launch the ONVIF PTZ controller
This made the demo stable and reproducible.
Security ObservationsThis project also collected several security-relevant observations.
The camera exposes:
22/tcp SSH
23/tcp Telnet
80/tcp HTTP
554/tcp RTSP
8000/tcp ONVIF / SOAPAdditional observations:
- RTSP traffic is unencrypted
- ONVIF runs over plain HTTP
- no TLS support was exposed for ONVIF
- HTTP redirects to HTTPS, but HTTPS on port 443 was not reachable
- default credentials remain active
- SSH is exposed
- Telnet is exposed
This is why I would not place this device on a trusted network.
Security Posture: Useful, but Intrinsically UntrustedOne important takeaway from this project is that low-cost consumer IoT cameras should be considered intrinsically untrusted devices.
The fact that the Tenda CP3 exposes RTSP and ONVIF locally is useful for interoperability, but it also highlights the broader security problem with this class of devices.
During testing, the camera exposed:
- factory default credentials
- RTSP over cleartext
- ONVIF over plain HTTP
- SSH
- Telnet
- inconsistent user management behavior
- no effective local TLS protection
This does not mean the device is useless.
It means it should be used only in the right context.
A device like this can be acceptable for:
- non-critical monitoring
- homelab experiments
- local automation
- smart mirror integrations
- temporary video checks
- isolated hobby projects
It should not be used as a trusted security component in critical environments.
I would not place this type of camera directly on a trusted LAN, and I would never expose it directly to the Internet.
The correct approach is isolation.
A safer deployment model is:
In practice:
- the camera should only talk to controlled local hosts
- RTSP and ONVIF should be blocked from the rest of the network
- no inbound Internet access should be allowed
- remote access should happen through a VPN such as Tailscale
- the device should be treated as replaceable and non-trusted
The main lesson is not simply that the camera can be controlled locally.
The lesson is that local control must be paired with proper network isolation.
Cheap consumer IoT devices are often powerful, but they are rarely designed with strong security boundaries.
This project does not make the device secure.
It makes the device understandable, controllable, and easier to isolate correctly.
Default Credentials and User ManagementThe credentials shown in this project are not leaked private credentials.
They are factory default credentials observed during testing, and their persistence is part of the security finding documented in this project.
The tested credentials were:
admin/admin123456They worked for:
- RTSP access
- ONVIF access
One of the most interesting findings was related to ONVIF user management.
Calling `GetUsers()` returned:
python
[]
even though authentication was successful.
Calling `SetUser()` returned success:
SetUser OKHowever, the password did not effectively change.
A dedicated script validates this behavior
python scripts/security/password-test.py <CAMERA_IP>The test performs:
1. login with the known working credentials
2. call ONVIF `SetUser()`
3. test the new password
4. test the original password again
Observed result:
SetUser returned OK
new password FAILED
old password STILL WORKS
This suggests that ONVIF user management is incomplete, ineffective, or disconnected from the actual authentication backend used by the camera.
RTSP Quality NotesThe RTSP stream works, but the implementation is not perfect.
During live playback, decoder warnings may appear:
rtsp: CSeq expected mismatch
hevc: Duplicate POC
hevc: Could not find ref with POC
Error parsing NAL unit
The stream still plays correctly.
This is typical of low-cost embedded RTSP implementations: functional, but not always perfectly standards-compliant.
Repository StructureThe project repository contains:
Main scripts:
- demo-local-control.shlaunches live video and keyboard PTZ control
- ptz-keyboard.pyinteractive keyboard PTZ controller
- security-scan.shcollects service exposure and protocol evidence
- security/onvif-info.pyretrieves ONVIF device information and capabilities
- security/password-test.pyvalidates ONVIF password-change behavior
Running the DemoInstall dependencies:
sudo apt update
sudo apt install -y python3 python3-venv ffmpeg nmap gnome-terminalCreate the Python virtual environment:
python3 -m venv onvif-venv
source onvif-venv/bin/activate
pip install -r requirements.txtVerify the ONVIF dependency:
python -c "import onvif; print('ONVIF OK')"Run the interactive local demo:
./scripts/demo-local-control.sh <CAMERA_IP>Example:
./scripts/demo-local-control.sh 192.168.123.123Demo VideoThe following video shows the local control demo in action:
https://www.youtube.com/watch?v=U-dyEM9EI2wThe demo shows:
- RTSP live video
- keyboard PTZ control
- local operation without vendor cloud
The Tenda CP3 is not technically a cloud-only device.
It exposes standard local protocols that allow:
- RTSP video access- ONVIF discovery
- ONVIF PTZ control
- local integration into self-hosted environments
The hardware is more capable than the official product experience suggests.
The cloud dependency appears to be primarily a product-layer design decision.
At the same time, the device should be treated carefully from a security perspective.
Default credentials, exposed Telnet/SSH, plain HTTP ONVIF, and inconsistent user management make network isolation mandatory.
For my use case, the right architecture is:
This provides the benefits of local control without directly trusting the camera.
The device is useful, but it should never be trusted.
Local control makes it more flexible, but proper isolation is mandatory.
Future WorkPossible next steps:
- snapshot extraction
- ONVIF event subscription
- motion event integration
- Android control application
- REST API wrapper
- WebRTC proxy for lower latency
- multi-camera orchestration
- isolated homelab deployment blueprint
This project documents interoperability testing performed on personally owned hardware.
The purpose is:
- protocol analysis
- local integration
- interoperability validation
- security evaluation
- self-hosted deployment research
Do not expose consumer IoT cameras directly to the Internet.














Comments