4
votes

PowerShell 6.2.1 doesn't return a Forms property using Invoke-WebRequest. The way I used to do it in Powershell 5:

$r = Invoke-WebRequest -Uri 'somewebsite.com' -SessionVariable f
$f
$form = $r.Forms[0]

$form.Fields["userName"] = "username"
$form.Fields["brokerPassword"] = "password"

$login = Invoke-WebRequest -Uri 'somewebsite.com' -WebSession $f -Method 
POST -Body $form.Fields

I see in the Microsoft documentation that there is now a -Form parameter you can use, but I've been hacking at it for a few hours now with very little success.

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-6

They provided two possible solutions.

Example 1

$Uri = 'https://api.contoso.com/v2/profile'
$Form = @{
firstName  = 'John'
lastName   = 'Doe'
email      = '[email protected]'
avatar     = Get-Item -Path 'c:\Pictures\jdoe.png'
birthday   = '1980-10-15'
hobbies    = 'Hiking','Fishing','Jogging'
}
$Result = Invoke-RestMethod -Uri $Uri -Method Post -Form $Form

Example 2

$Body = @{
User = 'jdoe'
password = 'P@S$w0rd!'
}
$LoginResponse = Invoke-WebRequest 'http://www.contoso.com/login/' - 
SessionVariable 'Session' -Body $Body -Method 'POST'

$Session

$ProfileResponse = Invoke-WebRequest 'http://www.contoso.com/profile/' - 
WebSession $Session

$ProfileResponse

I have tried both and my current code block is a combination of the two and it gets me at least a good failure.

$username = '[email protected]'
$password = 'p@ssw0rd'
$form = @{    
LoginForm_password = $password
LoginForm_username = $username
}
$result = Invoke-WebRequest 'https://www.podbean.com/login' - 
SessionVariable foo -Form $form -Method POST
$foo

$result | Get-Member | Format-table

This is the error I am now getting.

Invoke-WebRequest :

403 Forbidden

Forbidden You don't have permission to access /login on this server.

At line:7 char:11

  • $result = Invoke-WebRequest 'https://www.podbean.com/login' -SessionV ...
  •       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
  • CategoryInfo : InvalidOperation: (Method: POST, Reque\u2026tent-Length: 357 }:HttpRequestMessage) [Invoke-WebRequest], HttpResponseException
  • FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

This is actually the best response I've got.

1

1 Answers

3
votes

Why this probably isn't working

This site used Cross-Request Site Forgery protection, which hides a sneaky hidden textbox element in the form body, to ensure someone accesses the site before allowing them to login. You can see this for yourself when you inspect the HTML.

<div style="display:none">
    <input type="hidden" value="f8cbcfecec95c2405222c3cd0f951f6783aa329c" name="kdsowie31j4k1jlf913">
</div>

When I remove the CSS style elements of display:none and hidden, the hidden textbox appears.

enter image description here

When you've been logging in to this site normally, this value is submitted too and that's how they know it's you.

Since your requests so far with PowerShell have not included this critical value, this is likely why you have gotten 401 and other unauthorized errors.

You will have to retrieve this token and submit it as well for this to work. You cannot just retrieve it once and hard-code it into your scripts, as they values typically expire after first-usage or every so many hours. I tested and it looks like theirs expire very quickly.

Retrieving the token

It's not too hard to retrieve this token. Since it's the only hidden element on the form, we can use that attribute to retrieve it.

$Request = Invoke-WebRequest -Uri https://www.podbean.com/login -SessionVariable Session -UseBasicParsing -UseDefaultCredentials
$TokenValue = ''
ForEach($field in $Request.InputFields){
    if ($field.type -eq 'hidden'){
        $TokenValue = [psCustomObject]@{Name=$field.name;Value=$field.Value}
    "Token value found! `n`t`tElement Name:`t$($TokenValue.Name)`n`t`tElement Value:`t$($TokenValue.Value)"
    }
}

This will echo out the following when a Antiforgery/CRSF token value is found.

enter image description here

You can then bake this value into your form like you were trying to do previously, and see where it leads you.

  $header = @{
    '__RequestVerificationToken' = $TokenValue
}

$form = @{
    $TokenValue.Name = $TokenValue.Value
    LoginForm_password = $password
    LoginForm_username = $username
}

$result = Invoke-WebRequest 'https://www.podbean.com/login' -header $header -SessionVariable Session -Form $form -Method POST

You will have to play with this a bit, but I think this should get you on the right path. If it's not working, I'd remove the $header and see if that helps. I wrote a blog post on this topic and with greater info here, if you'd like to read more.

An easier route

PodBean also offers a API you can use to login to their services. This would be much, much simpler than what you're trying to do because instead of form scraping or web automation, you could do simple Rest method calls that would return JSON. 9 times out of 10, if an API exists you should use that instead for this simple reason:

When you use an API, there is a 'contract' that exists between you and the developer of the service. They will effectively promise not to change things too much so they don't break your workflow.

If you build an elaborate automation for this login system, expect it to break just frequently enough to be annoying, since they might change the login form, the CSS, or any of a number of things that would leave you dead in the water.

Here's how to get started with their API instead. They use oAuth as their authentication platform. I've got a guide on using PowerShell and oAuth here for you, if you get stuck with oAuth. If you proceed down the oAuth path and get stuck, make another post here and I'll help you there.