224

I am writing an HTTP server in C#.

When I try to execute the function HttpListener.Start() I get an HttpListenerException saying

"Access Denied".

When I run the app in admin mode in windows 7 it works fine.

Can I make it run without admin mode? if yes how? If not how can I make the app change to admin mode after start running?

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        private HttpListener httpListener = null;

        static void Main(string[] args)
        {
            Program p = new Program();
            p.Server();
        }

        public void Server()
        {
            this.httpListener = new HttpListener();

            if (httpListener.IsListening)
                throw new InvalidOperationException("Server is currently running.");

            httpListener.Prefixes.Clear();
            httpListener.Prefixes.Add("http://*:4444/");

            try
            {
                httpListener.Start(); //Throws Exception
            }
            catch (HttpListenerException ex)
            {
                if (ex.Message.Contains("Access is denied"))
                {
                    return;
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
Randall Flagg
  • 4,834
  • 9
  • 33
  • 45
  • If someone want to avoid that error he can try writing it with TcpListener. It doesn't require admin privileges – Vlad Mar 29 '17 at 09:26
  • I face the same issue, in Visual Studio 2008 + Windows 7, it produce 'Access denied' error, to counter solve this is to run the Visual Studio 2008 in Admin Mode – Mark Khor Dec 17 '19 at 20:06
  • 1
    What is this madness? I've never seen these hoops opening a non-privileged port in any other language, on any operating system. I don't even know what they're trying to accomplish – Michael Mrozek Jan 12 '23 at 04:54

12 Answers12

356

Yes you can run HttpListener in non-admin mode. All you need to do is grant permissions to the particular URL. e.g.

netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user

Documentation is here.

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • 58
    This is helpful, but for completeness, the URL specified in this line of code: `httpListener.Prefixes.Add("http://*:4444/");` must match EXACTLY with the one in the `netsh` command. For example, I had `httpListener.Prefixes.Add("http://127.0.0.1:80/");` and the same `netsh` command you have, and the HttpListenerException will still be thrown. I needed to change `httpListener.Prefixes.Add("http://+:80/");`Thanks for your help @Darrel Miller, because you got me on the right path to figuring this out! – psyklopz Jun 01 '12 at 23:58
  • 13
    And don't forget the trailing slash if "MyUri" is empty, otherwise you'll get a `The parameter is incorrect` error. Example: `url=http://+:80/` – Igor Brejc May 16 '13 at 17:53
  • 16
    Is there any way to do this for a non-administrative user, even for `http://localhost:80/`? I have a desktop application that needs to receive one request on such a URL, and it seems a shame to require that an administrator install it on 50 desktops, just for this one purpose. – John Saunders Jan 06 '14 at 04:38
  • 7
    Apparently not. There's also no mention of elevation or administrative privileges being required in the documentation page. So it's easy to assume that this will act as a "smarter" TCPListener, when in reality there is MAJOR reason not to use it - yet this isn't documented. – David Ford Aug 08 '16 at 10:13
  • @DavidFord There are workarounds https://leastprivilege.com/2007/03/23/punching-holes-into-http-sys/ The other option is to use Kestrel because it doesn't use http.sys – Darrel Miller Aug 09 '16 at 14:49
  • @DarrelMiller - thanks for the thoughts, however the key point I'm making here is that Microsoft's documentation on this topic does -not- indicate the major issues and headaches deploying any code written using HTTP.sys. I've written considerable software, as well as making assumptions that just "listening on a port" would be a trivial exercise (as it is in the raw sockets API's). This ended up being completely wasted effort as the code can't be deployed without elevation. A switch to Kestrel is non-trivial. – David Ford Sep 12 '16 at 07:19
  • @DavidFord I do agree it is a real pain. And very unfortunate that the MSDN docs make no mention of it. Unfortunately, limiting malware from opening local servers is a necessary evil. Based on your comment about the switch to Kestrel being non-trivial, might I suggest future efforts are based on the OWIN abstraction. That makes switching hosts much easier. – Darrel Miller Sep 12 '16 at 13:20
  • @DavidFord I'm running into same issue! All I can think to do is create a separate .bat script to setup the netsh ports necessary for the app to run. Such a script would only need to be executed once by a sysadmin (say, at installation time if your .MSI foo is strong). From my experience, it's a system setting that survives restarts and can be applied globally (to all users). – FizxMike Aug 31 '17 at 13:57
  • 4
    Running netsh requires admin ;( – superfly71 May 22 '18 at 10:23
  • Quick way to just make this work without knowing much else is to set the domain to Everyone and Host to '+': netsh http add urlacl url=http://+:8282/ user=Everyone listen=yes – zezba9000 Nov 14 '18 at 02:28
  • 4
    @DarrelMiller this doesn't stop malware from opening local servers. You can still create an HTTP server, you just can't do it using HTTP.sys or `HttpListener`, e.g. one can call `TcpListener.Create(4444).Start()` and implement HTTP/1.1 over it. So I don't see why this (seemingly undocumented) permission check exists. – Qwertie Oct 17 '19 at 18:40
33

If you use http://localhost:80/ as a prefix, you can listen to http requests with no need for Administrative privileges.

Ehsan Mirsaeedi
  • 6,924
  • 1
  • 41
  • 46
  • 2
    Can you post some example code? I just tried this with `http://localhost:80/` and got an "Access Denied". – John Saunders Jan 06 '14 at 04:35
  • 2
    Sorry, that doesn't seem to work. Please check if you're running as administrator, which shouldn't be the normal case. – Jonno Jun 27 '14 at 02:08
  • if this would work, i would consider it a windows bug. no matter what you think about windows, i assume this to be of a very basic level that very, very likely they did not miss to handle correctly. – hoijui Oct 19 '15 at 08:06
  • 40
    So more than 2 years later, this works for me now on Windows Server 2008 R2 with .NET framework 4.5. `httpListener.Prefixes.Add("http://*:4444/");` indeed shows an `Access Denied` error but `httpListener.Prefixes.Add("http://localhost:4444/");` work without any problem. It looks like Microsoft excluded `localhost` from these restrictions. Interestingly, `httpListener.Prefixes.Add("http://127.0.0.1:4444/");` still shows an Access Denied error, so the only thing that works is `localhost:{any port}` – Tony Feb 02 '16 at 12:28
  • 3
    @Tony thanks your comment was actually the most valuable for me. I had "127.0.0.1" in several configs. QA reported that it wasn't working on Windows 8.1 but was in Windows 10 just fine (and I tested it myself in Win10). Oddly, it seems Microsoft has Everyone granted to use 127.0.0.1 in Win10 but not 8.1 (and presumably prior versions), but sure enough, localhost is fine. Thanks – Adam Plocher Mar 29 '16 at 13:47
  • @Tony ah shit, it requires that host header now though :(, so browsing to http://127.0.0.1 from a browser. This is a problem since I need to expose it externally from a dumb TCP proxy with no knowledge of host headers. – Adam Plocher Mar 29 '16 at 13:59
  • Also, doesn't work for me if I try "localhost" instead of "+" in netsh add urlacl command... apparently the urlacl junk is not consistent with httplistener. – FizxMike Aug 31 '17 at 14:08
  • @Tony http://127.0.0.1:4444/ seems to work for 127 and localhost at the same time. If you listen only on localhost then the 127 does not work. same behavior with your PcName vs your ip address. A specific IP seems to allow name and ip. – Sql Surfer Nov 07 '18 at 16:26
  • @Tony I confirm that your solution works on Windows 10. Imo, this is a much better solution than running netsh to grant privileged. – Philip Attisano Aug 16 '19 at 04:41
30

Can I make it run without admin mode? if yes how? If not how can I make the app change to admin mode after start running?

You can't, it has to start with elevated privileges. You can restart it with the runas verb, which will prompt the user to switch to admin mode

static void RestartAsAdmin()
{
    var startInfo = new ProcessStartInfo("yourApp.exe") { Verb = "runas" };
    Process.Start(startInfo);
    Environment.Exit(0);
}

EDIT: actually, that's not true; HttpListener can run without elevated privileges, but you need to give permission for the URL on which you want to listen. See Darrel Miller's answer for details.

Community
  • 1
  • 1
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Can you please explain why I have to start with elevated privileges? – Randall Flagg Oct 26 '10 at 10:53
  • You also need to add this line 'startInfo.UseShellExecute = false;' before 'Process.Start(startInfo);' – Randall Flagg Oct 26 '10 at 11:33
  • @Randall: because that's how Windows works... a process can't switch to admin mode while it's running. Regarding UseShellExecute: it depends on what you're executing. I tested my code with "notepad.exe", it works fine without UseShellExecute = false – Thomas Levesque Oct 26 '10 at 12:05
  • Thanks. About the UseShellExecute: I tried to run the code I posted. Another problem is that for some reason it asked me once if I want to run as administrator and any other time after that it doesn't ask. I restarted, Debugged to make sure it goes there and nothing. any suggestions? – Randall Flagg Oct 26 '10 at 13:39
  • Not really... perhaps the second time you were already running it as admin ? – Thomas Levesque Oct 26 '10 at 14:17
  • I decided to try and disable the 'startInfo.UseShellExecute = false;' and it worked perfectly. Thank you very very much :) – Randall Flagg Oct 26 '10 at 15:10
  • 1
    @Thomas There is no problem running HttpListener in non-admin mode. – Darrel Miller Nov 06 '10 at 21:46
25

The syntax was wrong for me, you must include the quotes:

netsh http add urlacl url="http://+:4200/" user=everyone

otherwise I received "The parameter is incorrect"

Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
Dave
  • 629
  • 6
  • 11
18

As an alternative that doesn't require elevation or netsh you could also use TcpListener for instance.

The following is a modified excerpt of this sample: https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp

// Generates state and PKCE values.
string state = randomDataBase64url(32);
string code_verifier = randomDataBase64url(32);
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));
const string code_challenge_method = "S256";

// Creates a redirect URI using an available port on the loopback address.
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
output("redirect URI: " + redirectURI);

// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
    authorizationEndpoint,
    System.Uri.EscapeDataString(redirectURI),
    clientID,
    state,
    code_challenge,
    code_challenge_method);

// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);

// Waits for the OAuth authorization response.
var client = await listener.AcceptTcpClientAsync();

// Read response.
var response = ReadString(client);

// Brings this app back to the foreground.
this.Activate();

// Sends an HTTP response to the browser.
WriteStringAsync(client, "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please close this window and return to the app.</body></html>").ContinueWith(t =>
{
    client.Dispose();
    listener.Stop();

    Console.WriteLine("HTTP server stopped.");
});

// TODO: Check the response here to get the authorization code and verify the code challenge

The read and write methods being:

private string ReadString(TcpClient client)
{
    var readBuffer = new byte[client.ReceiveBufferSize];
    string fullServerReply = null;

    using (var inStream = new MemoryStream())
    {
        var stream = client.GetStream();

        while (stream.DataAvailable)
        {
            var numberOfBytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
            if (numberOfBytesRead <= 0)
                break;

            inStream.Write(readBuffer, 0, numberOfBytesRead);
        }

        fullServerReply = Encoding.UTF8.GetString(inStream.ToArray());
    }

    return fullServerReply;
}

private Task WriteStringAsync(TcpClient client, string str)
{
    return Task.Run(() =>
    {
        using (var writer = new StreamWriter(client.GetStream(), new UTF8Encoding(false)))
        {
            writer.Write("HTTP/1.0 200 OK");
            writer.Write(Environment.NewLine);
            writer.Write("Content-Type: text/html; charset=UTF-8");
            writer.Write(Environment.NewLine);
            writer.Write("Content-Length: " + str.Length);
            writer.Write(Environment.NewLine);
            writer.Write(Environment.NewLine);
            writer.Write(str);
        }
    });
}
Sebastian
  • 6,293
  • 6
  • 34
  • 47
Michael Olsen
  • 404
  • 3
  • 14
  • This was really useful as we had the HttpListener issue when implementing an IBrowser for the IdentityModel.OidcClient library so a very similar use case to the above. – mackie Aug 30 '17 at 11:28
  • 1
    It's worth noting that the code above has bugs in it though. Firstly using a StreamWriter with UTF8 causes issues in some browsers, I'm guessing because of a BOM being output or something like that. I changed it to ASCII and all is well. I also added some code to wait for data to become available for read on the stream as it would sometimes return no data. – mackie Sep 06 '17 at 08:56
  • Thanks @mackie - that part was taken directly from Microsoft's own sample actually. I guess they didn't really test it properly. If you can edit my answer go ahead and fix the methods - otherwise maybe send me the changes and I can update it. – Michael Olsen Sep 07 '17 at 10:20
  • 3
    Instead of using ASCII one should use the following constructor ```new UTF8Encoding(false)```, that disables emitting of a BOM. I've changed this already in the answer – Sebastian Oct 03 '17 at 13:46
  • I prefer this solution over the accepted answer. Running netsh seems like a hack. This is a solid programmatic solution. Thanks! – Jim Gomes May 09 '18 at 18:20
15

In case you want to use the flag "user=Everyone" you need to adjust it to your system language. In english it is as mentioned:

netsh http add urlacl url=http://+:80/ user=Everyone

In german it would be:

netsh http add urlacl url=http://+:80/ user=Jeder
Christoph Brückmann
  • 1,373
  • 1
  • 23
  • 41
9

By default Windows defines the following prefix that is available to everyone: http://+:80/Temporary_Listen_Addresses/

So you can register your HttpListener via:

Prefixes.Add("http://+:80/Temporary_Listen_Addresses/" + Guid.NewGuid().ToString("D") + "/";

This sometimes causes problems with software such as Skype which will try to utilise port 80 by default.

Sebastian
  • 6,293
  • 6
  • 34
  • 47
8

You can start your application as administrator if you add Application Manifest to your project.

Just Add New Item to your project and select "Application Manifest File". Change the <requestedExecutionLevel> element to:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
R.Titov
  • 3,115
  • 31
  • 35
8
httpListener.Prefixes.Add("http://*:4444/");

you use "*" so you execute following cmd as admin

netsh http add urlacl url=http://*:4444/ user=username

no use +, must use *, because you spec *:4444~.

https://msdn.microsoft.com/en-us/library/system.net.httplistener.aspx

Yirkha
  • 12,737
  • 5
  • 38
  • 53
NamedStar
  • 81
  • 1
  • 1
3

I also faced similar problem.If you have already reserved url then you have to first delete the url to run in non admin mode else it will fail with Access is Denied error.

netsh http delete urlacl url=http://+:80
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
1

Thanks all, it was of great help. Just to add more [from MS page]:

Warning

Top-level wildcard bindings (http://*:8080/ and http://+:8080) should not be used. Top-level wildcard bindings can open up your app to security vulnerabilities. This applies to both strong and weak wildcards. Use explicit host names rather than wildcards. Subdomain wildcard binding (for example, *.mysub.com) doesn't have this security risk if you control the entire parent domain (as opposed to *.com, which is vulnerable). See rfc7230 section-5.4 for more information.

fcdt
  • 2,371
  • 5
  • 14
  • 26
0

Unfortunately, for some reasons probably linked with HTTPS and certificates, the native .NET HttpListener requires admin privileges, and even for HTTP only protocol...

The good point

It is interesting to note that HTTP protocol is on top of TCP protocol, but launching a C# TCP listener doesn't require any admin privileges to run. In other words, it is conceptually possible to implement an HTTP server which do not requires admin privileges.

Alternative

Below, an example of project which doesn't require admin privileges: https://github.com/EmilianoElMariachi/ElMariachi.Http.Server

Kino101
  • 765
  • 8
  • 18