0

I am trying to connect to a website programatically, and have the following basic code so far but the responseString just returns the websites Login page in html and doesn't actually login to the website:

    var client = new HttpClient();

    var values = new Dictionary<string, string>
    {
        { "username", "[email protected]" },
        { "password", "pass1234" }
    };

    var content = new FormUrlEncodedContent(values);

    var response = await client.PostAsync("https://www.website.com/Portal/open/Logon.aspx", content);

    var responseString = await response.Content.ReadAsStringAsync();

    Console.WriteLine(responseString);

The Request and Form Data when I log in through the actual website is shown below:

The Request and Form Data is shown below:

Request Header
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 991
Content-Type: application/x-www-form-urlencoded
Cookie: ASP.NET_SessionId=ABCD
Host: www.website.com
Origin: https://www.website.com
Referer: https://www.website.com/Portal/open/Logon.aspx
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36

Form Data
__LASTFOCUS: 
__EVENTTARGET: 
__EVENTARGUMENT: 
__VIEWSTATE: /ABCC
__VIEWSTATEGENERATOR: 123456
__SCROLLPOSITIONX: 0
__SCROLLPOSITIONY: 0
__EVENTVALIDATION: /ABCD
chatQueueId: 127
chatPartition: 258TEL
chatDisplayType: dialog
chatTitle: WebLink Support
guestName: Guest
ctl00$ContentPlaceHolder1$Login1$UserName: [email protected]
ctl00$ContentPlaceHolder1$Login1$Password: pass1234
ctl00$ContentPlaceHolder1$Login1$LoginButton: Log In

I read that there are numerous ways to do this but I haven't figured it out, hence this post.

I have gone through the suggestions below but getting a "NotFound" response and I think it is due to the javascript call below in the form.

<input type="submit" name="ctl00$ContentPlaceHolder1$Login1$LoginButton" value="Log In" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$ContentPlaceHolder1$Login1$LoginButton&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" id="ctl00_ContentPlaceHolder1_Login1_LoginButton" class="submitBtn" />

Thank you for the responses so far.

7
  • You need to make your request look exactly like the browser request. Right now, it isn't going to (eg ctl00$ContentPlaceHolder1$Login1$UserName: [email protected]) Commented Oct 16, 2020 at 1:26
  • If the site is not yours and is well constructed, you wont do it because that you want to do is a lack of security. Commented Oct 16, 2020 at 1:27
  • IMHO Load the login form, including server cookies, parse the html, find any hidden inputs, post that. Commented Oct 16, 2020 at 1:28
  • @JeremyLakeman I am not exactly too sure how to do that? Are you able to give me an example? Commented Oct 16, 2020 at 1:29
  • Couple issues here. Any well (or half decent) designed website will not let you do this for security reasons. Log-in forms generally use anti-forgery tokens or other means to ensure this kind of thing doesn't happen. If you want to authenticate with a website, you need to go through OAuth2, OIDC or one of the other various protocols. We also can't tell if you're even hitting the right endpoint to submit the form, without us seeing the real website. Is there a reason you need to do this? Commented Oct 16, 2020 at 1:30

2 Answers 2

1

Submitting a html form, needs to protect against a number of different attacks that could be performed in javascript while you are browsing other web sites. Any form should include a code unique to your request, that you have to include when you submit the form again. Otherwise anyone would be able to post a new facebook status, a new tweet, transfer funds from your bank account, ... etc.

Here's something similar that I've written before. The basic idea is to fetch the login form, parse it with Html Agility Pack, fill in values we know about, then submit it;

// ...
var handler = new HttpClientHandler {
    AllowAutoRedirect = false,
    CookieContainer = new CookieContainer()
};

var client = new HttpClient(handler);
client.BaseAddress = new Uri("...");
// ...

private async Task Login(string username, string password)
{
    var response = await client.GetAsync("/Account/Login", HttpCompletionOption.ResponseHeadersRead);

    // if the server thinks we are already logged in
    if (response.StatusCode == HttpStatusCode.Found
        && response.Headers.Location.ToString().Contains("/Dashboard/Welcome"))
        return;

    if (response.StatusCode!=HttpStatusCode.OK)
        throw new Exception($"Login failed (GET /Account/Login returned {response.StatusCode})");

    var doc = new HtmlDocument();
    doc.LoadHtml(await response.Content.ReadAsStringAsync());
    var forms = doc.DocumentNode.Descendants("form");
    foreach(var form in forms)
    {
        var action = form.Attributes["action"].Value;
        // identify the correct form based on where the action posts to
        if (!action.StartsWith("/Account/Login"))
            continue;

        // find all inputs, visible or hidden
        var values = new Dictionary<string, string>();
        var inputs = form.SelectNodes("//input[@name]");
        foreach(var input in inputs)
        {
            var name = input.Attributes["name"]?.Value;
            if (string.IsNullOrWhiteSpace(name))
                continue;
            var value = input.Attributes["value"]?.Value;

            // substitute required values we know about
            if (name.ToLowerInvariant().Contains("username"))
                value = username;
            else if (name.ToLowerInvariant().Contains("password"))
                value = password;

            if (!string.IsNullOrWhiteSpace(value))
                values[name] = value;
        }

        // submit the form
        var loginResponse = await client.PostAsync(action, new FormUrlEncodedContent(values));
        if (loginResponse.StatusCode != HttpStatusCode.Found)
            throw new Exception($"Login failed (POST {action} returned {loginResponse.StatusCode})");

        return;
    }
    throw new Exception("Login failed");
}

The above could be generalised to submit any basic html form of any web site. This is not fool-proof, a web site could be doing something else with javascript in order to login.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi Jeremy, thank you for the response. The form does do something with javascript and I've added this to the description of my ticket. Any ideas on what I could do here please?
0

I'm a .Net guy but I happen to have the Python code to illustrate what issue I think you're facing. This example logs into the Microsoft Certification portal. The problem I hit was the Microsoft Certification portal has a __RequestVerificationToken and this is what @JeremyLakeman was talking about with submitting a form.

Python:

with requests.Session() as session:        
    page = session.get('https://mcp.microsoft.com/Anonymous/transcript/validate', verify=False)
    soup = BeautifulSoup(page._content, 'html.parser')
    
    payload = {
        'transcriptId': '725151', 
        'accessCode': 'jeremythompson'
        }

    requestToken = ''
    hidden_tags = soup.find_all("input", type="hidden")
    for tag in hidden_tags:
        if tag.attrs["name"] == '__RequestVerificationToken':
            requestToken = tag.attrs["value"]
            break

    payload['__RequestVerificationToken'] = requestToken

    page = session.post('https://mcp.microsoft.com/anonymous/transcript/validate', data=payload)

See how I look for the hidden __RequestVerificationToken input and then pass it back in the payload.

C#:

var client = new HttpClient();

var values = new Dictionary<string, string>
{
    { "username", "[email protected]" },
    { "password", "pass1234" }
};

var requestToken = "";
var inputs = form.SelectNodes("//input[@name]");
foreach(var input in inputs)
{
     if (input.attrs["name"] == "__RequestVerificationToken") {
        requestToken = input.attrs["value"];
        break;
     }
}

values.Add("__RequestVerificationToken", requestToken);
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://www.website.com/Portal/open/Logon.aspx", content);

var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);

2 Comments

Hi Jeremy, how did you figure out there was that extra __RequestVerificationToken element? I updated my ticket because there is a javascript call and I'm not too sure what to do here?
I think I just used the Developer Tools in Chrome and probably found it looking at the page html source code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.