logo
CHEATSHEETSTOOLSABOUT中文

Python Challenge - Level 13

Problem

The image shows a telephone keypad with button "5" being clickable. Clicking it links to ../phonebook.php, which returns an XML response:

<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>105</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>XML error: Invalid document end at line 1, column 1</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>

This indicates an XML-RPC endpoint. The clue from Level 12 was "phone that evil", and evil4.jpg mentioned "Bert is evil! go back!". Therefore, we should phone Bert.

Solution

Python

Python includes xmlrpc.client in the standard library:

import xmlrpc.client

conn = xmlrpc.client.ServerProxy("http://www.pythonchallenge.com/pc/phonebook.php")

# Introspection
print(conn.system.listMethods())
# ['phone', 'system.listMethods', 'system.methodHelp', 'system.methodSignature', ...]

# Call the method
response = conn.phone("Bert")
print(response)  # 555-ITALY

The returned phone number is 555-ITALY. The key word is italy.

Go

Since XML-RPC is just an HTTP POST with a standard XML envelope, we can either construct the XML payload directly using standard net/http or use an XML-RPC library:

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"regexp"
)

func main() {
	xmlPayload := `<?xml version="1.0"?>
<methodCall>
  <methodName>phone</methodName>
  <params>
    <param><value><string>Bert</string></value></param>
  </params>
</methodCall>`

	resp, err := http.Post(
		"http://www.pythonchallenge.com/pc/phonebook.php",
		"text/xml",
		bytes.NewBufferString(xmlPayload),
	)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	re := regexp.MustCompile(`<string>(.*?)</string>`)
	matches := re.FindSubmatch(body)
	if len(matches) > 1 {
		fmt.Printf("Phone: %s\n", matches[1]) // 555-ITALY
	}
}

Rust

In Rust, we post the XML-RPC body via ureq (or reqwest):

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload = r#"<?xml version="1.0"?>
<methodCall>
  <methodName>phone</methodName>
  <params>
    <param><value><string>Bert</string></value></param>
  </params>
</methodCall>"#;

    let response = ureq::post("http://www.pythonchallenge.com/pc/phonebook.php")
        .set("Content-Type", "text/xml")
        .send_string(payload)?
        .into_string()?;

    if let Some(start) = response.find("<string>") {
        if let Some(end) = response[start..].find("</string>") {
            let phone = &response[start + 8..start + end];
            println!("Phone: {phone}"); // 555-ITALY
        }
    }

    Ok(())
}

Language Comparison

Feature Python Go Rust
XML-RPC Support Built-in stdlib (xmlrpc.client) with dynamic method dispatch via ServerProxy No stdlib XML-RPC; simple via standard net/http POST or kolo/xmlrpc No stdlib; third-party crates (dxr, reqwest) or raw HTTP POST
RPC Abstraction Dynamic method lookup via __getattr__ makes remote calls look like local functions Explicit request structs / marshaling Explicit types with Serde deserializers
Network stdlib urllib / xmlrpc.client Batteries-included production-grade net/http Minimal stdlib; network clients rely on ecosystem (reqwest, ureq)

Key insight: Python's dynamic runtime enables ServerProxy magic, where unknown method names on the object automatically map to XML-RPC method names without pre-generated stubs. In static languages like Go and Rust, sending raw XML over standard HTTP is trivial, robust, and requires zero external RPC framework dependencies.

Next Level

Replace disproportional with italy:

http://www.pythonchallenge.com/pc/return/italy.html