2025-07-22 12:03:00
blog.kulkan.com
I’ve executed penetration testing of multiple applications in the past, some of which offered among their features the ability to protect online streaming of assets with a Watermark, and each implementation had its own peculiarities. By Assets I mean video, images and PDFs. Interestingly enough, some of the techniques covered in this post may also apply to Paywall implementations.
In this blog post, I wanted to share my experience testing different sample watermarking implementations and ways in which I, along with the rest of the team here in Kulkan, have managed to circumvent intended protections.
I’m not going to be covering attacks related to Digital Rights Management (DRM) implementations, although in one of the cases below we will look into a typical key-retrieval and decryption process which, depending on how it gets implemented, could be DRM-like. If you’re interested in learning more about DRM for Web/mobile streaming, a good starting point is looking into the different Browser/Platform-based frameworks listed below:
This post contains my experience strengthening the security of Watermark-based solutions for our customers.
Before we dive in: I strongly discourage Piracy and Illegal distribution of proprietary content. I’m hoping the information contained in this post will either help the reader improve the security of their implementations, or help testers and developers improve the security of their [customer] implementations.
Watermarks come normally with one or more of 3 main use-cases:
- Copyright; watermarks can visibly indicate the rightful owner of the content, serving as a means of asserting copyright.
- Discouraging unauthorized distribution; by explicitly displaying on screen an ID or authentication-string bound to the identity of the entity or person displaying the content. This clearly does not prevent theft or distribution, but it is meant to introduce a level of accountability.
- Forensics and traceability. In addition to the last use-case where its incentive works best when the watermark is visible on screen, there can also be hidden or invisible watermarks which enable the tracing of a specific asset back to the identity of the party that rendered, streamed, or reproduced it, supporting post-distribution investigations.
This section covers a series of Watermark implementations I’ve taken a shot at during penetration testing in the past; they do not represent a complete list of alternatives — you will likely run into so many more!
Removing a trivial HTML element
The simplest and least-secure implementation of a Watermark. Implemented via a floating div or HTML element that sits on top of the asset. Removing the Watermark is as simple as removing the HTML element. Very similar to poorly implemented Paywalls. No security whatsoever, pretty straightforward.
JavaScript based Watermarks
A more advanced version of the simple HTML-based Watermark. The element is created dynamically and monitored from JavaScript. If you try to remove the element, it gets re-created, and/or an exception handler takes care of stopping the reproduction of the asset abruptly to prevent it from being displayed without the watermark. In these cases, the JavaScript code is normally obfuscated and contains as many anti-debugging features as possible without breaking its intended functionality.
Ways to try and circumvent these implementations include:
Checking if the JavaScript file is required for the asset to play.
Otherwise intercepting a server-response and directly removing the file might lead to a bypass. Refer to sections below for more information on this.
Beautifying the code and debugging it to..
- Learn how the Loading/Drawing/Stream-Stopping process is implemented.
- Learn to patch any of the aforementioned flows.
Playing with Styles
An approach I’ve used in the past involves messing with CSS and Styles. If you can’t modify the element directly, you may be able to apply changes in style to it. For example, stuff I’ve tried in the past includes:
- Hiding the element or modifying its height/width.
- Changing the element color properties (eg background same as text, transparency, …)
- Making the font really small, or really, really big.
- Messing with its x/y position on screen.
Others discussed in sections below.
Picture in Picture
Most browsers have introduced support for Picture in Picture (PiP) in streaming/playback of video, a feature that has already been standardized across all major browsers and is documented in W3C: https://www.w3.org/TR/picture-in-picture/. Forcing PiP on a video element is as easy as calling its requestPictureInPicture() method:
To my surprise, there was a time in which I managed to easily escape from a Watermark that was enforced on a highly controlled video element by turning on Picture in Picture! Plus, the resulting floating window can be resized at your convenience.
When the User becomes the Attacker
It is a typical mistake in client-side Watermark implementations to forget that the attacker is (with exceptions, e.g. DRM) in full control of the client logic and the data exchanged between the client and the server; which is why many resort to obfuscation to achieve security through obscurity.
Subresource Integrity used incorrectly.
I once ran into an implementation that relied on a JavaScript-based Watermark, and the logic resided in a JavaScript file which was loaded in HTML with SRI in order to prevent tampering. The JavaScript was not only in charge of the Watermark but also loading and playing assets; therefore it was required in order for the assets to be displayed.
For anyone unaware of how SRI works, it is a security feature that allows browsers to verify that resources (e.g. scripts) fetched from third-party servers haven’t been tampered with. It works by including a cryptographic hash (like SHA-256) of the file in the tag which fetches the file. When the browser downloads the resource, it calculates its own hash and compares it to the one provided. If they don’t match, the browser refuses to execute or apply the resource, protecting users from compromised CDN files or man-in-the-middle attacks.
The customer got surprised when I showed him that I could just remove SRI altogether by intercepting the server response, before the browser even loaded the stream, that way my browser would not even know it had to enforce the protection.
Disabling Picture in Picture
Another typical mistake may be to rely on the ability to Disable Picture-in-Picture as described in W3 at: https://www.w3.org/TR/picture-in-picture/#disable-pip — Disabling PiP should be viewed as a user experience feature, not a security mechanism. Users can easily bypass this restriction and re-enable PiP, even in complex scenarios where the video element is continuously recreated and all related attributes are reapplied. Mozilla in its documentation states that Firefox users can even ignore requests to disable PiP altogether by changing a value in User configuration.
Messing with the content of the Watermark
Let’s assume for a second that you can’t break the logic which renders and displays the Watermark. An implementation I once tested displayed, as part of the watermark, the name of the account streaming the asset. And guess what? The name was configurable through the user’s profile. If it isn’t the name of the account streaming, it could as easily be the username, or some other piece of information that could be altered by the attacker prior to reproducing content.
In a way this relates to “injection”, because even if the attacker only partially controls the data displayed in the Watermark, the data under control could include enough characters (or non-ASCII) to force the sensitive data out of view.
In the sections above we’ve mostly looked at circumventing protections enforced by the designated player or client; but what if we look behind the scenes at how that video is being delivered to the client and we were able to just save the video, and reproduce it elsewhere? It is a common misconception to believe that, disregarding whether a video stream is watermarked or not, that it can’t be reproduced outside the Origin or the player where it was meant to be streamed.
I’m going to focus on HLS (HTTP Live Streaming) as it is a widely adopted method for delivering video over the Internet, and it is also the method I’m experienced in.
If the implementation uses HLS, it relies on the client-side player retrieving an m3u8 file with pointers to locations/URLs of video and audio segments. An m3u8 file is highly configurable and can support multiple resolutions, audio streams, and more. Refer to RFC8216 for detailed information on HLS and m3u8 files, available at:
Our ability to eavesdrop on, and capture, traffic whether by inspecting browser network activity (in the case of browser-based players) or by intercepting it through a tool such as PortSwigger Burp or OWASP ZAP will allow us to inspect/analyze the contents of the m3u8 playlist file.
However, capturing just the m3u8 playlist isn’t always sufficient, at least not if we intend to re-create those assets locally and become free of having to load them each time we want to reproduce the video. Along those lines, we also need to be aware that video and audio segments are often stored at temporary, access-controlled locations (e.g. stored in an S3 bucket, with a signature that expires an hour after creation.) In such cases, it’s crucial to not only capture the playlist but also download each segment promptly to preserve the full stream offline. And “promptly” may not even do it if we’re looking at one-time URLs, which is why proxying and capturing the traffic during playback will be critical.
And if you’re looking at sensitive assets, it will likely be the case that each of the segments may be encrypted. Fortunately, HLS encryption is typically designed to secure content during transit rather than impose complex DRM. The m3u8 file usually specifies the encryption method and includes the URI of the key, along with any necessary parameters such as the Initialization Vector (IV), especially when using ciphers like AES-128-CBC. This information can be used to decrypt the media segments and reconstruct the stream for offline playback.
The following sections walk you through different alternatives at the time of downloading segments and recreating videos for offline playback. Needless to say, and back to the main point of this article, this would only get rid of a Watermark if it was not burnt into the video itself, may that be visually or invisibly.
Delegating the work to ffmpeg
ffmpeg is a widely adopted open-source tool used to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created, and it can read from an arbitrary number of inputs (which can be regular files, pipes, network streams, grabbing devices, etc).
And it accepts an m3u8 playlist as input. The following sample script will read from a m3u8 playlist, and save the video to disk after decoding each segment:
PLAYLIST="https://xxxxxxx/playlist.m3u8"
OUTPUT="saved_video.mp4"
ffmpeg -i "$PLAYLIST" -crf 50 $OUTPUT
Manually decrypting segments
The following sample m3u8 excerpt references an encryption Key and an Initialization Vector that were used to encrypt a video segment:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:16
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-KEY:METHOD=AES-128,URI="encryption_key",IV=0x764062cf53cf5e569f1323151dc38bec
#EXTINF:15.701333,
stream000000.ts
#EXT-X-ENDLIST
In order to decrypt the contents of stream000000.ts we’ll first get the contents of the key, the segment, and the IV. And we can then rely on openssl to decrypt its contents:
KEY=$(cat encryption_key | hexdump -e '16/1 "%02x"')
IV=764062cf53cf5e569f1323151dc38bec
openssl aes-128-cbc -d -in stream000000.ts -out test.ts -K $KEY -iv $IV
We should now be able to play segments directly, or we could create a new m3u8 playlist which references the decrypted segment and no encryption information whatsoever:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:16
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:15.701333,
decrypted_segment_000000.ts
#EXT-X-ENDLIST
Scripting the decryption with m3u8_decrypt.py
Even if ffmpeg already solves your current use-case; or openssl may look like enough to take care of the job; there may be scenarios in which you may need to, or want to, script the decryption process.
That is why I put together m3u8_decrypt.py which we’ve published on our GitHub repository at:
The script uses Python’s cryptographic libraries to:
- Download an encrypted HLS stream (.m3u8 and associated segments)
- Fetch decryption keys
- Decrypt each video segment
- Save the decrypted segments locally
While watermarking can add an important layer of accountability and deterrence to content distribution, many implementations are easily bypassed when challenged by an attacker with client-side control. If you’re developing a secure streaming solution, consider combining watermarking with hardened delivery mechanisms, such as server-side rendering or DRM, where applicable.
Sebastian Savini [LinkedIn] [X]
Security Consultant @ Kulkan
Kulkan Security (www.kulkan.com) is a boutique offensive security firm specialized in Penetration Testing. If you’re looking for a Pentest partner, we’re here. Reach out to us via our website, at www.kulkan.com
More on Kulkan at:
Subscribe to our newsletter at:
Keep your files stored safely and securely with the SanDisk 2TB Extreme Portable SSD. With over 69,505 ratings and an impressive 4.6 out of 5 stars, this product has been purchased over 8K+ times in the past month. At only $129.99, this Amazon’s Choice product is a must-have for secure file storage.
Help keep private content private with the included password protection featuring 256-bit AES hardware encryption. Order now for just $129.99 on Amazon!
Help Power Techcratic’s Future – Scan To Support
If Techcratic’s content and insights have helped you, consider giving back by supporting the platform with crypto. Every contribution makes a difference, whether it’s for high-quality content, server maintenance, or future updates. Techcratic is constantly evolving, and your support helps drive that progress.
As a solo operator who wears all the hats, creating content, managing the tech, and running the site, your support allows me to stay focused on delivering valuable resources. Your support keeps everything running smoothly and enables me to continue creating the content you love. I’m deeply grateful for your support, it truly means the world to me! Thank you!
BITCOIN bc1qlszw7elx2qahjwvaryh0tkgg8y68enw30gpvge Scan the QR code with your crypto wallet app |
DOGECOIN D64GwvvYQxFXYyan3oQCrmWfidf6T3JpBA Scan the QR code with your crypto wallet app |
ETHEREUM 0xe9BC980DF3d985730dA827996B43E4A62CCBAA7a Scan the QR code with your crypto wallet app |
Please read the Privacy and Security Disclaimer on how Techcratic handles your support.
Disclaimer: As an Amazon Associate, Techcratic may earn from qualifying purchases.