On WordPress Pingbacks
WordPress supports Pingbacks and these are enabled by default. Having noticed a missing pingback from one of my posts and having made sure that it wasn’t caught as spam or still pending, I decided to investigate a bit and try to resend it manually.
Armed with the Pingback specification, and the XML-RPC RFC, I was able to successfully have a request cURLed over.
curl "https://.../xmlrpc.php" --header "Content-Type: text/xml" --data "<?xmlversion="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>https://.../</string></value></param><param><value><string>https://.../</string></value></param></params></methodCall>"
<?xml version="1.0"?> <methodCall> <methodName>pingback.ping</methodName> <params> <param> <value><string>https://.source./</string></value> </param> <param> <value><string>https://.target./</string></value> </param> </params> </methodCall>
For which I got a nice response:
<?xml version="1.0"?> <methodResponse> <params> <param> <value> <string>Pingback from https://.source./ to https://.target./ registered. Keep the web talking! :-)</string> </value> </param> </params> </methodResponse>
Mission accomplished, got the Pingback thorough, great! But what if…?
Almost immediately, lots of thoughts bounce into my head, both good and evil. But I won’t be cURLing aimlessly around and trying interesting things out, but will go read the WordPress source on how it implements its Pingback server and client.
The Pingback client
Client-side Pingbacks are sent using the do_all_pings
function. This function is triggered via the do_pings
action. This hook is scheduled to be pulled inside the _publish_post_hook function, which is called once a post is (surprise-surprise) published.
Back to the do_all_pings
function, a query is run selecting all “unpung” posts, deleting each and every ping flag as it goes (ah, so no matter what happens [Pingback server not responding] it gets Pingbacked once and only once.
The actual Pingback function is here.
Some things to note:
- A Pingback request is aborted after only 3 seconds
- A 60-second
set_time_limit
is set regardless of what the actual time limit is - There is no way to get any error messages or requeue a Pingback
The combination of these three factors have almost surely stopped my pingback from reaching its destination, due to a network jam or some other problems. Of course, they’re there for a reason, but still, come on.
add_ping
is called if the response code from the Pingback server is true or its error is set to 48
, meaning “Pingback already registered”.
Get all your WordPress outgoing Pingbacks via SQL: SELECT pinged FROM wp_posts WHERE `pinged` != '';
. These are \n
delimited and have to be split outside of MySQL.
Problem remains – once a Pingback fails, it’s over. It doesn’t get requeued, no second chance, is there? You can’t even call the do_all_pings
since it takes into account only posts that have their _pingme
key set. You can still pingback()
a post directly, which is good to know.
A nice thing to have would be something like “Out of X URLs in this post Y Pingbacks have been successfully received” and a [Re-ping] button. Will probably code this later this week for myself. Unless there’s already something of the like or someone beats me to it…
The Pingback server
Receiving Pingbacks in WordPress is handled by the built-in WordPress XML-RPC Server. The method for pingbacks is pingback.ping
and takes two arguments as per specification.
While you’re there check out the pingback.extensions.getPingbacks
method.
curl "codeseekah.com/xmlrpc.php" --header "Content-type: text/xml" --data "<?xml version="1.0"?><methodCall><methodName>pingback.extensions.getPingbacks</methodName><params><param><string>https://codeseekah.com/2012/03/11/ack-grep-vs-grep/</string></param></params></methodCall>" <?xml version="1.0"?> <methodResponse> <params> <param> <value> <array><data> <value><string>https://ecojoy.se/2012/03/10/ack-grep-vs-grep/</string></value> </data></array> </value> </param> </params> </methodResponse>
Handy! And not even part of any specification, although it did sort of aim to be part of the Pingback 2.0 spec, which hasn’t been released (yet?). This method hustled itself into WordPress 7 years ago. A bit of trivia there.
Back on track.
ping.pingback
checks whether the second parameter (pagelinkedto
) contains its domain. After a series of attempts a post_id
is identified.
Then checks are performed to make sure the post has Pingbacks enabled, and to make sure that a Pingback has not already been posted (this is done against the comments table). The pinging server is then given 1 second of idle time to publish before trying to retrieve the page, after which a wp_remote_fopen
call is made.
The data that comes back is analyzed and made sure to have:
- A title! Yes, some people like to have no titles on their pages, Pingbacks from untitled posts (a rarity, but still) will be ignored.
- The URL has to be present on the page and in a link context, meaning inside the
href
attribute of an anchor tag.
Once all these tests are passed – we’re good to go with a nice “Pingback from %1$s to %2$s registered. Keep the web talking! :-)” response. Yeehaaaw!
Stay tuned!
[…] Published 4 hours ago […]
Awesome post, and so nice to read! I’m glad I subscribed to your feed! 🙂
Keep it up!
Thanks for dropping by, Konstantin. Glad you enjoyed it 😉
[…] Pingback AttackYesterday I wrote a post titled On WordPress Pingbacks. While writing this I came to several conclusions that resulting in some interesting experiments […]
[…] Pingbacks have been part of the WordPress since the very beginning. One of my previous articles, titled WordPress Pingback Attacks explores two types of denial-of-service attacks that leverage Pingback request processing in WordPress. If you do not know how Pingbacks work, I suggest taking a quick crash-course here. […]
Is the first
.../xmlrpc.php
supposed to be replaced withhercool.wordpress.com/xmlrpc.php
or … ?Yes,
.../xmlrpc.php
has to have the website you’re trying to reach prepended.curl "https://yoursite.com/xmlrpc.php" ...and so on
Thank you. That worked as it should and is probably the only way to send pingbacks from tumblr to wordpress.
There was, though, an unsolvable problem with one of the pingbacks (of several) that I sent.
curl ".../xmlrpc.php" --header "Content-Type: text/xml" --data "<?xmlversion="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>.../post/24522850119/geocentric/</string></value></param><param><value><string>.../2011/06/22/but-it-doesn%e2%80%99t-move/</string></value></param></params></methodCall>"
faultCode: 33, faultString: The specified target URL cannot be used as a target. It either doesn’t exist, or it is not a pingback-enabled resource.
In the case of this URL, the
’
is converted to%e2%80%99
and that seems to cause an unresolvable difficulty.For the non-weird URL’s, your method worked perfectly. Thanks.
Glad it worked. The URL is completely fine, I would think that pingbacks are simply disabled for that post that’s it; “it is not a pingback enabled resource”. The author must have set the “Allow pingbacks and trackbacks on this page.” setting to off; are you able to verify this?
I also tried pinging by post ID directly on https://thonyc.wordpress.com/?p=1081 and got a different response:
The source URL does not contain a link to the target URL, and so cannot be used as a source.
, since your page doesn’t contain that link, try changing it and firing it off by post ID instead.Let me know how it goes. I am not even able to reproduce such a weird slug, WordPress strips that character out for me, I’ll try on WordPress.com.
P.S. When pasting code with tags (in the XML payload) and other HTML-ish special characters, they have to be escaped, otherwise WordPress will either treat them as HTML, or strip their tags out. You can sanitize your HTML code for paste in comments online here https://centricle.com/tools/html-entities/
If you add a link to your source ID on your page, you should get the error I did. (The pingback crawls the post you linked from to make sure you, erm, actually linked from it. Not that this is much of a security improvement, but at least it’s a slight hump those who would abuse the system have to get over.)
Whoops, sorry, I’m 90% asleep and misunderstood you. Yes! That worked. Wow. how did you figure out
?p=1081
was the “true” (slugless) post name? (Also you figured out I was trying to ping thony christie’s blog — very observant!)That post is actually not disabled, because your
?p=1081
suggestion worked. I did need to also change my link to the slugless version though. Which must mean that WordPress’ pingback checker doesn’t make “obvious” conversions (not hooked into its own API’s or something) — rather it just checks for exactly the text you say you’ve linked to.And thanks for the bonus tip as well!
I see, so it’s a bug when looking resolving the post by slug, however the slug should have never contained those characters in the first place. As for finding out the post ID, one way is to look at the source code, the HTML classes and id’s will give out the ID of the post; the content div would have a class of
post-####
in WordPress standards-compliant themes.I succeeded again using your method after I linked to
phoenixandolivebranch.wordpress.com
, but a WordPress-hosted site that uses its own URL / A record says it haswww.johndcook.com/xmlrpc.php
(if I curl tojohndcook.com/xmlrpc.php
) but upon curling to that address I get a404
page.I guess it is the history of spammers abusing the trackback system that made pingbacks more difficult.
curl https://www.johndcook.com/xmlrpc.php --header "Content-Type: text/xml" --data "<?xmlversion="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http.../post/26028530787/identity-matrix-in-r-cran-statistical-language</string></value></param><param><value><string>.../blog/2012/06/13/matrix-condition-number/#comment-184566</string></value></param></params></methodCall>"
John’s blog is installed under the /blog directory. The
xmlrpc.php
file is there, too (https://www.johndcook.com/blog/xmlrpc.php
). I’ve never seen spammers use pingbacks to do anything, it’s not efficient as not only would they have to find pingback-enabled resources and pass the spam filter, but mount real links back, too (temporarily, but still).If I want to ping A site, is A target site or souce site? I am confused. BTW, great post.
Depends on what sort of attack you’re performing. If you want to DoS site A you can either send it pingbacks with a huge download body so it runs out of resources fast (this has been patched in later versions of WordPress), or you can send a pingback to sites B, C, D, E, F, G, H, I, J, K .. with site A address and have them DoS sites A with lots of HTTP requests without requiring owned machines; any WordPress site can be coerced into attacking any other site. This still works really well and we’ve seen it being exploited many times throughout the past 4-5 months (look for 499 nginx status code on xmlrpc.php).