Carlo 4561 Posted November 3, 2024 Posted November 3, 2024 On 10/29/2024 at 5:43 PM, rbjtech said: I'm not sure it's technically possible, which is why I asked. There are no unique 'keys' in the URL, which is why it's so easy to bypass auth. If the embyid was unique and impossible to guess for each item, then we would not be having this discussion and while obfuscation should never replace authentication - it would be acceptable to do it this way this imo. I personally don't use nginx so I don't know the specifics or actual implementation for Emby, but I can give you some ideas to try. EDIT: I removed what I original wrote. Basically, it used a session tracking in nginx. It could get things like the device id, emby token, username, device name, from a few places but theideal place would be setting a rule on a URL starting with "http://server:port/emby/Users/authenticatebyname" You then process the response to response which has everything you need to know it's a authorized user. I had other trips written such as calling the API or a request to a URL that would require authorization (using headers nginx received). If emby gives you back a response they would be authenticated and if emby gives you an error on your known URL then they aren't authenticated. You could then update the session variable to reflect the auth status. So, you could have a rule on "http://{public domain}/emby/items/{increment number}/images/primary". You do the check as mentioned with the received headers from the inbound request and if it's successful you would pass the URL to Emby Server which would return an item pic. You could also set up a rule looking at http://{public domain}/webhooks and when executed you would parse the payload which has user information. You then match that up to a session on nginx and with the success or failure notification you receive from webhooks. EASIER METHOD Use the nginx secure links module. Available to compile in your own nginx if needed but built into most binary releases. Pretty decent explanation and use here: https://www.f5.com/company/blog/nginx/securing-urls-secure-link-module-nginx-plus This is one of the examples, but I'm not fond of it as is. I would not bind an images to an IP, nor would I use an expiration date on the command line. Every rewrite of an image would have a different expires in the URL which would break caching if using a CDN like Cloudflare. For that matter it would break nginx caching as well if used. I'm referring to the ability to have all users getting the same URL for graphic links. It wouldn't matter if your proxy or a CDN has a "private" graphic cached as no one would know it even exists unless they were logged in to your system and received a rewrite URL with the hash in it. Example I don't like but it's helpful to understand how it would work. location /files { root /var/www; secure_link $arg_md5,$arg_expires; secure_link_md5 "$secure_link_expires$uri$remote_addr enigma"; if ($secure_link = "") { return 403; } if ($secure_link = "0") { return 410; } } User clicks a link /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740 The location is /files which matches so it sets the root to /var/www It has two arguments defined which is md5 and expires. These variables are pulled from the URL so: md5=UEnXC7T-Tfv9WLsWbf-mw expires=1483228740 It then calls a function secure_link_md5 passing the expires, the URL and the remote IP address and "enigma" <--- secret word secure_link_md5 function check the date and if expired sets $secure_link= 0. If not expired secure_link_md5 generates an md5 checksum on the info passed in (expires, uri, remote ip, "enigma"). This checksum is compared to $arg_mf5. if it doesn't match $secure_link is set to an empty string "". If it does match it will build the new URL by starting with the root defined as "/var/www", followed by the location which is "/files" and finally the $uri variable which is "/playlist.htm" giving is /var/www/files/pricelist.htm It would then send this request on to the server. Another example and closer to how we should implement it. No expire or binding to IP. server { listen 80; server_name secure-link-demo; location /videos { secure_link_secret enigma; if ($secure_link = "") { return 403; } rewrite ^ /secure/$secure_link; } location /secure { internal; root /opt; } } User clicks on a link "https://videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8" and after being parsed is matched to the location /videos. This time we take our uri which is "/hls/bunny.m3u8" and our secret "enigma" to generate an md5. If our generrated md5 doesn't match the URL md5 we set $secure_link = "", otherwise we set $secure_link to the URI. We then rewrite it to /secure/$secure_link which becomes "/secure/hls/bunny.m3u8 " Then then gets processed by the location /secure which is "internal" meaning it only applies to internally created URLs. This will take the URL it's processing starting with /secure and prepends It with the root variable and end up with "/opt/secure/hls/bunny.m3u8" It should be easy to see how that could be used with Emby to block direct access to the images. Person Opinion on Implementation I really wouldn't want the EmbyID (graphicI) used directly in the link where it's easy to see. I'd probably take the ID and pad it with 4 characters then the ID and then another 4 characters. Assuming the code runs really quick I'd even try something like creating a hash of the EmbyID. Then grab the first X characters then Y characters after that. For the URI I could then use X+ID+Y. That should be similar to creating random characters for padding. You could get fancy like generate 2 random numbers from 1 to 9. With the first random number you would grab Random1-1 chars from the junk hash and then Random2-1 chars from the just has. Create the URI string by writing Random1 number + first set of chars + ID + Random2 chars + R2 number.. Later when you need to get your actual ID back look at the first number of the URI and strip that many chars of the front. Look at the number at the end of the string and strip that many from the end of the URI leaving you with the actual ID. Only reason to do that at all is to have a slick way to hide the ID in the URI so it's not in the same place all the time but would float around, making it harder to figure out a pattern. I'm sure that last bit is way overkill for most people that just want to prevent people from being able to use the same URL over and over again just adding 1 to the ID while being able to access all your pics. Just follow the "bunny" example which should be pretty easy and will get the job done. Carlo 1
rbjtech 5284 Posted November 3, 2024 Posted November 3, 2024 (edited) Thanks Carlo - I'll have a play when I get some time. But I do need to ask the question on why Emby cannot simply do this at source for the item id ? All 'new' itemid's on future items get the random id, all previous ones continue to work. or of course you can regenerate the id's. I would love to introduce a proper alphanumber id - but this would mean client/api updates as I believe it's an interger atm. QueryResult_BaseItemDto{ Items [BaseItemDto{...}] TotalRecordCount integer($int32) } Edited November 3, 2024 by rbjtech
Carlo 4561 Posted November 3, 2024 Posted November 3, 2024 If I remember correctly the graphics used to use a GUID ID. That made it pretty hard to guess so not requiring authentication wasn't as much an issue as it would be difficult to guise it. A couple/few releases back this was changed to IDs vs the GUID ID. I believe the ID was the index in the database making it faster to acquire. Luke or ebr, feel free to correct me if I got that wrong. rbjtech, I also wonder what kind of performance hit the server would take if it checked to see if the connection requesting the image is logged in and has access to that specific content. Even if we skipped a check for specific access to said content and only checked to see if the session was authenticated would stop random people/scripts from being able to get any images. @Luketake a look a couple post above with a method I posted that can be implemented in nginx. Could we not do something very similar? 1. Add a config field to server/settings page for admin to enter a secret_code. When generating the links instead of: http(s)://{public domain}/emby/items/ID/images/primar you generate a md5 hash on ID using the secret_code The url becomes: http(s)://{public domain}/emby/hash/items/ID/images/primar Now when a request comes in you don't need to check if authenticated as they would have needed to have access already to get the URLs All you do is a quick check by generating a md5 on the ID using the system's secret_code to see if it matches the hash passed on the URI. Without a person knowing the secret_code they would not be able to generate a hash to match the ID stopping random URL requests dead. The only way a person would have the proper URL to retrieve the graphics would be if they were logged in and authenticated at the time the links were generated. I'd think this would run extremely fast on the server side, especially compared to any type of database lookup. 1
rbjtech 5284 Posted November 4, 2024 Posted November 4, 2024 17 hours ago, Carlo said: If I remember correctly the graphics used to use a GUID ID. That made it pretty hard to guess so not requiring authentication wasn't as much an issue as it would be difficult to guise it. A couple/few releases back this was changed to IDs vs the GUID ID. I believe the ID was the index in the database making it faster to acquire. Luke or ebr, feel free to correct me if I got that wrong. rbjtech, I also wonder what kind of performance hit the server would take if it checked to see if the connection requesting the image is logged in and has access to that specific content. Even if we skipped a check for specific access to said content and only checked to see if the session was authenticated would stop random people/scripts from being able to get any images. @Luketake a look a couple post above with a method I posted that can be implemented in nginx. Could we not do something very similar? 1. Add a config field to server/settings page for admin to enter a secret_code. When generating the links instead of: http(s)://{public domain}/emby/items/ID/images/primar you generate a md5 hash on ID using the secret_code The url becomes: http(s)://{public domain}/emby/hash/items/ID/images/primar Now when a request comes in you don't need to check if authenticated as they would have needed to have access already to get the URLs All you do is a quick check by generating a md5 on the ID using the system's secret_code to see if it matches the hash passed on the URI. Without a person knowing the secret_code they would not be able to generate a hash to match the ID stopping random URL requests dead. The only way a person would have the proper URL to retrieve the graphics would be if they were logged in and authenticated at the time the links were generated. I'd think this would run extremely fast on the server side, especially compared to any type of database lookup. Yep - anything to randomise a direct URL would be more than suitable - and this is exactly what others do from my limited testing. As an example - I can re-use a direct URL from my Amazon photo's - but the URL is unique to that image. It has a TTL as if I re-get that image later, the unique URL has changed. I'm not suggesting Emby need to do that, but the fact it is an unguessable URL means it's 'safe' to cache in the first place without needing Auth. 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now