[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fQbXto6GCBrz8tN_gC6VdPy6pwBgcUeo3ZeqDd445094":3},{"id":4,"question":5,"answer":6,"answerHtml":7,"slug":8,"keywords":9,"article":10,"status":33,"aiModel":30,"aiConfidence":30,"updatedAt":49,"createdAt":49,"_status":48},989,"What is the correct way to upload a file using the clientUploader plugin, and what common mistake should be avoided?","To upload a file, send a POST to `\u002Fservice\u002Fextension\u002FclientUploader\u002Fupload` with `Content-Type: multipart\u002Fform-data; boundary=...` and include a `Cookie: ZM_ADMIN_AUTH_TOKEN`. A common mistake is manually setting the `Content-Type` header with a fixed boundary; this causes the request library to regenerate a different boundary, breaking the upload. Instead, use the `requests_toolbelt` library's `MultipartEncoder` to ensure the boundary matches exactly. This upload capability is part of the expanded features described in [Zimbra SOAP API Development Guide 2](\u002Fnews\u002Fzimbra-soap-api-development-guide-2).","\u003Cp>To upload a file, send a POST to `\u002Fservice\u002Fextension\u002FclientUploader\u002Fupload` with `Content-Type: multipart\u002Fform-data; boundary=...` and include a `Cookie: ZM_ADMIN_AUTH_TOKEN`. A common mistake is manually setting the `Content-Type` header with a fixed boundary; this causes the request library to regenerate a different boundary, breaking the upload. Instead, use the `requests_toolbelt` library&#39;s `MultipartEncoder` to ensure the boundary matches exactly. This upload capability is part of the expanded features described in [Zimbra SOAP API Development Guide 2](\u002Fnews\u002Fzimbra-soap-api-development-guide-2).\u003C\u002Fp>\u003Cp>\u003Ca href=\"\u002Fnews\u002Fzimbra-soap-api-development-guide-2\">Read the related One Day Sec article\u003C\u002Fa>\u003C\u002Fp>","what-is-the-correct-way-to-upload-a-file-using-the-clientuploader-plugin-and-wha-1777481051715","clientUploader, file upload, multipart\u002Fform-data, requests_toolbelt, MultipartEncoder, Zimbra admin",{"id":11,"title":12,"slug":13,"description":14,"content":15,"contentHtml":27,"cover":30,"author":31,"views":20,"readingTime":32,"status":33,"publishedAt":34,"seo":35,"tags":39,"qaPairs":40,"meta":45,"updatedAt":46,"createdAt":47,"_status":48},243,"Zimbra SOAP API Development Guide 2","zimbra-soap-api-development-guide-2","Learn Zimbra SOAP API admin features: get user tokens, upload files via clientUploader, and detect logs with Python examples.",{"root":16},{"type":17,"format":18,"indent":19,"version":20,"children":21,"direction":29},"root","",0,1,[22],{"type":23,"format":18,"indent":19,"version":20,"children":24,"direction":29},"paragraph",[25],{"mode":26,"text":27,"type":28,"style":18,"detail":19,"format":19,"version":20},"normal","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody onload=\"window.parent._uploadManager.loaded(20000001,'null');\">\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article \"Zimbra SOAP API Development Guide\" introduced the method of calling the Zimbra SOAP API, along with the open-source code Zimbra_SOAP_API_Manage.\u003C\u002Fp>\u003Cp>This article will expand on that foundation by adding features that can be achieved using administrator privileges.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Obtaining the token of a specified mailbox user\u003C\u002Fli>\u003Cli>Uploading files to the server via the clientUploader plugin\u003C\u002Fli>\u003Cli>Log detection\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Obtaining the token of a specified mailbox user\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Documentation: https:\u002F\u002Ffiles.zimbra.com\u002Fdocs\u002Fsoap_api\u002F8.8.15\u002Fapi-reference\u002FzimbraAdmin\u002FDelegateAuth.html\u003C\u002Fp>\u003Cp>The corresponding namespace is zimbraAdmin\u003C\u002Fp>\u003Cp>The requested address is: uri+\":7071\u002Fservice\u002Fadmin\u002Fsoap\"\u003C\u002Fp>\u003Cp>According to the SOAP format in the documentation, it can be implemented with the following Python code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def gettoken_request(uri,token):\u003Cbr>    print(\"[*] Input the mailbox:\")\u003Cbr>    mail = input(\"[&gt;]: \")\u003Cbr>    request_body=\"\"\"\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>         \u003Cdelegateauthrequest xmlns=\"urn:zimbraAdmin\">\u003Cbr>            \u003Caccount by=\"name\">{mail}\u003C\u002Faccount>        \u003Cbr>         \u003C\u002Fdelegateauthrequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003Cbr>    \"\"\"    \u003Cbr>    try:\u003Cbr>        print(\"[*] Try to get the token\")\u003Cbr>        r=requests.post(uri+\":7071\u002Fservice\u002Fadmin\u002Fsoap\",data=request_body.format(token=token,mail=mail),verify=False,timeout=15)\u003Cbr>        if 'authToken' in r.text:\u003Cbr>            pattern_token = re.compile(r\"\u003Cauthtoken>(.*?)\u003C\u002Fauthtoken>\")\u003Cbr>            token = pattern_token.findall(r.text)\u003Cbr>            print(\"[+] authTOken:%s\"%(token[0]))\u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003Cbr>        exit(0)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The returned result is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Csoap:header>\u003Ccontext xmlns=\"urn:zimbra\">\u003C\u002Fcontext>\u003C\u002Fsoap:header>\u003Csoap:body>\u003Cdelegateauthresponse xmlns=\"urn:zimbraAdmin\">\u003Cauthtoken>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\u003C\u002Fauthtoken>\u003Clifetime>3600000\u003C\u002Flifetime>\u003C\u002Fdelegateauthresponse>\u003C\u002Fsoap:body>\u003C\u002Fsoap:envelope>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Extracting the authToken value can be used for mailbox login. The login method is as follows:\u003C\u002Fp>\u003Cp>Navigate to the Zimbra mailbox login web page and add the following Cookie information:\u003C\u002Fp>\u003Cp>Name: ZM_AUTH_TOKEN\u003C\u002Fp>\u003Cp>Value: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\u003C\u002Fp>\u003Cp>On the login page, enter the mailbox username (no password required) and click login.\u003C\u002Fp>\u003Cp>Method to add Cookie in Chrome browser:\u003C\u002Fp>\u003Cp>Press F12 in Chrome browser to open Developer Tools, select the Application tab.\u003C\u002Fp>\u003Cp>Expand Storage -&gt; Cookies sequentially.\u003C\u002Fp>\u003Ch2>0x03 Upload files to the server via the clientUploader plugin.\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Note: The file upload operation requires setting Content-Type in the request headers to multipart\u002Fform-data; boundary=${boundary}. Example format:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Content-Type: multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Complete packet format example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>POST \u002Fservice\u002Fextension\u002FclientUploader\u002Fupload HTTP\u002F1.1\u003Cbr>Host: mail.xx.com\u003Cbr>Proxy-Connection: keep-alive\u003Cbr>Cache-Control: max-age=0\u003Cbr>Upgrade-Insecure-Requests: 1\u003Cbr>User-Agent: Mozilla\u002F5.0 (Windows NT 10.0; WOW64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F63.0.3239.132 Safari\u002F537.36\u003Cbr>Accept: text\u002Fhtml,application\u002Fxhtml+xml,application\u002Fxml;q=0.9,image\u002Fwebp,image\u002Fapng,*\u002F*;q=0.8\u003Cbr>Accept-Encoding: gzip, deflate\u003Cbr>Accept-Language: zh-CN,zh;q=0.9\u003Cbr>Content-Type: multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno\u003Cbr>Content-Length: 400\u003Cbr>Cookie: ZM_ADMIN_AUTH_TOKEN=0_530bf417d0f3e55ed628e4671e44b1dea4652bab_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313535343835323934303131393b61646d696e3d313a313b\u003Cbr>Upgrade-Insecure-Requests: 1\u003Cbr>\u003Cbr>------WebKitFormBoundary1abcdefghijklmno\u003Cbr>Content-Disposition: form-data; name=\"file\"; filename=\"test.jsp\"\u003Cbr>Content-Type: image\u002Fjpeg\u003Cbr>\u003Cbr>test12345\u003Cbr>------WebKitFormBoundary1abcdefghijklmno--\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Here, ------WebKitFormBoundary1abcdefghijklmno is the delimiter, and ------WebKitFormBoundary1abcdefghijklmno-- is the terminator.\u003C\u002Fp>\u003Cp>If this attribute is not set, the file upload operation will fail, returning the following result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>The request does not upload a file\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>During the Python script development process, if the attribute is directly added in the Headers: Content-Type: multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno, the sample code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>headers[\"Content-Type\"]=\"multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>This will cause a bug and prevent successful execution.\u003C\u002Fp>\u003Cp>The packet format at this point is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>POST \u002Fservice\u002Fextension\u002FclientUploader\u002Fupload HTTP\u002F1.1\u003Cbr>Host: mail.xx.com\u003Cbr>Proxy-Connection: keep-alive\u003Cbr>Cache-Control: max-age=0\u003Cbr>Upgrade-Insecure-Requests: 1\u003Cbr>User-Agent: Mozilla\u002F5.0 (Windows NT 10.0; WOW64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F63.0.3239.132 Safari\u002F537.36\u003Cbr>Accept: text\u002Fhtml,application\u002Fxhtml+xml,application\u002Fxml;q=0.9,image\u002Fwebp,image\u002Fapng,*\u002F*;q=0.8\u003Cbr>Accept-Encoding: gzip, deflate\u003Cbr>Accept-Language: zh-CN,zh;q=0.9\u003Cbr>Content-Type: multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno\u003Cbr>Content-Length: 400\u003Cbr>Cookie: ZM_ADMIN_AUTH_TOKEN=0_530bf417d0f3e55ed628e4671e44b1dea4652bab_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313535343835323934303131393b61646d696e3d313a313b\u003Cbr>Upgrade-Insecure-Requests: 1\u003Cbr>\u003Cbr>------3c4bc2fbc2368a87e5def7b234fd126b\u003Cbr>Content-Disposition: form-data; name=\"file\"; filename=\"test.jsp\"\u003Cbr>Content-Type: image\u002Fjpeg\u003Cbr>\u003Cbr>test12345\u003Cbr>------3c4bc2fbc2368a87e5def7b234fd126b--\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>It was found that the delimiter and terminator are randomly regenerated values, not the ----WebKitFormBoundary1abcdefghijklmno we set in the request headers.\u003C\u002Fp>\u003Cp>Therefore, the Python code needs to be modified. Here is one solution: use the requests_toolbelt library.\u003C\u002Fp>\u003Cp>Code example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>    fileContent = 0;\u003Cbr>    path = input(\"[*] Input the path of the file:\")\u003Cbr>    with open(path,'r') as f:\u003Cbr>        fileContent = f.read()\u003Cbr>    filename = path\u003Cbr>    print(\"[*] filepath:\"+path)\u003Cbr>    print(\"[*] filedata:\"+fileContent)\u003Cbr>\u003Cbr>    headers = {\u003Cbr>    \"Content-Type\":\"application\u002Fxml\"\u003Cbr>    }   \u003Cbr>    headers[\"Content-Type\"]=\"multipart\u002Fform-data; boundary=----WebKitFormBoundary1abcdefghijklmno\"\u003Cbr>    headers[\"Cookie\"]=\"ZM_ADMIN_AUTH_TOKEN=\"+token+\";\"\u003Cbr>\u003Cbr>    m = MultipartEncoder(fields={\u003Cbr>    'filename1':(None,\"test\",None),\u003Cbr>    'clientFile':(filename,fileContent,\"image\u002Fjpeg\"),\u003Cbr>    'requestId':(None,\"12345\",None),\u003Cbr>    }, boundary = '----WebKitFormBoundary1abcdefghijklmno')\u003Cbr>\u003Cbr>    r = requests.post(uri+\"\u002Fservice\u002Fextension\u002FclientUploader\u002Fupload\",data=m,headers=headers,verify=False)\u003Cbr>    if 'window.parent._uploadManager.loaded(1,' in r.text:\u003Cbr>        print(\"[+] Upload Success!\")\u003Cbr>        print(\"[+] URL:%s\u002Fdownloads\u002F%s\"%(uri,filename))\u003Cbr>    else:\u003Cbr>        print(\"[!]\")\u003Cbr>        print(r.text)  \u003Cbr>        exit(0)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>After successful upload, the path is in the downloads directory, accessible only to verified users\u003C\u002Fp>\u003Ch2>0x04 Open Source Code\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The new code has been uploaded to GitHub, address as follows:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Added the following two features:\u003C\u002Fp>\u003Cul>\u003Cli>Gettoken\u003C\u002Fli>\u003Cli>upload\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Simultaneously added support for the CVE-2019-9621 SSRF vulnerability, enabling access to management resources via the SSRF exploit when the mail server's 7071 management port is closed.\u003C\u002Fp>\u003Ch2>0x05 Log Detection\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The location of login logs is \u002Fopt\u002Fzimbra\u002Flog\u002Fmailbox.log\u003C\u002Fp>\u003Cp>For other types of mail logs, refer to https:\u002F\u002Fwiki.zimbra.com\u002Fwiki\u002FLog_Files\u003C\u002Fp>\u003Ch2>0x06 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article expands the invocation methods of the Zimbra SOAP API, adding two practical functions: obtaining tokens for specified mailbox users and uploading files to the server via the clientUploader plugin, documenting the implementation details.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","text","ltr",null,"Onedaysec",3,"published","2026-02-02T07:25:19.986Z",{"title":36,"description":14,"keywords":37,"ogImage":30,"canonicalUrl":30,"noIndex":38},"Zimbra SOAP API Admin Guide: Token, Upload, Logs","Zimbra SOAP API, admin token, file upload, log detection, Python code",false,[],{"docs":41,"hasNextPage":38},[42,43,4,44],991,990,988,{"title":30,"description":30,"image":30},"2026-07-24T02:07:15.799Z","2026-07-23T16:02:22.816Z","draft","2026-07-23T16:15:56.624Z"]