jacobeturpin / python-screenconnect

An unofficial Python implementation of the ScreenConnect API
MIT License
13 stars 1 forks source link

Support for 2FA #1

Open SethCalkins opened 7 years ago

SethCalkins commented 7 years ago

Our SC uses 2FA in addition to user Pass .. is there any way to implement this?

jacobeturpin commented 7 years ago

Need to investigate how this would be implemented.

As of the latest stable build (6.2.12963), the application's authentication is handled via an ASP.NET web form rather than exposing an API. This issue likely is not a trivial change unless the core product's APIs are altered.

jacobeturpin commented 5 years ago

I was able to get a very rough prototype of this working through a simple script. The following will load the login page, parse for necessary values like the ASPX view state, and submit the appropriate values.

import json
import requests
from bs4 import BeautifulSoup

url = 'https://{removed}.screenconnect.com'
user = 'test'
pwd = '{removed}'

sess = requests.session()

r = sess.get(url + '/Login')
soup = BeautifulSoup(r.content)

user_name_box = soup.find('input', {'id': 'Main_userNameBox'})
un_name = user_name_box.attrs['name']
pwd_box = soup.find('input', {'id': 'Main_passwordBox'})
pwd_name = pwd_box.attrs['name']
view_state = soup.find('input', {'id': '__VIEWSTATE'})
vs_hash = view_state.attrs['value']
vs_generator_box = soup.find('input', {'id': '__VIEWSTATEGENERATOR'})
vs_gen = vs_generator_box.attrs['name']

payload = {
    '__LASTFOCUS': None,
    '__EVENTTARGET': None,
    '__EVENTARGUMENT': None,
    '__VIEWSTATE': vs_hash,
    '__VIEWSTATEGENERATOR': vs_gen,
    un_name: user,
    pwd_name: pwd,
    'ctl00$Main$loginButton': 'Login'
}
r1 = sess.post(url + '/Login', data=payload)
soup2 = BeautifulSoup(r1.content)

view_state = soup2.find('input', {'id': '__VIEWSTATE'})
vs_hash = view_state.attrs['value']
vs_generator_box = soup2.find('input', {'id': '__VIEWSTATEGENERATOR'})
vs_gen = vs_generator_box.attrs['name']
mfa_box = soup2.find('input', {'id': 'Main_oneTimePasswordBox'})
mfa_name = mfa_box.attrs['name']
mfa_code = input('Enter MFA code:  ')

payload2 = {
    '__LASTFOCUS': None,
    '__EVENTTARGET': None,
    '__EVENTARGUMENT': None,
    '__VIEWSTATE': vs_hash,
    '__VIEWSTATEGENERATOR': vs_gen,
    mfa_name: mfa_code,
    'ctl00$Main$loginButton': 'Login'
}
r2 = sess.post(url + '/Login', data=payload2)

r3 = sess.get(url + '/Services/PageService.ashx/GetHostSessionInfo')  # Confirm auth w/ this endpoint
print(json.loads(r3.content))

r4 = sess.post(url + '/Login?Reason=7')  # Logout
sess.close()

I'll take this and begin refactoring the authentication logic to conditionally check if MFA is present or not, and prompt for MFA if necessary. This will likely only support the user-input code MFA flow at first, as it won't be robust enough to wait for something like Duo's push notifications for consent initially.