Because if you use a vbo, you would need 4 vertices per particles, but the ubo can just hold one entry (the position) per particle.
I really don't understand why you say make a UBO for XY positions of the particles. Why not just another VBO?Is it because of a GPU memeory placement thing? Because I would need VAO for it, where I can get away with not having one if I use a UBO?
Your understanding of vbos and ubos is correct. However this is like a hack that works really well. And it works well because GPUs are getting more and more similar to CPUs, and vbos and ubos is just memory getting fetched, that the api put arbitrary restrictions that are no longer necessary.
By setting a null vbo and draw 4 vertices (1 particle), you're basically telling the api "draw 4 vertices of nothing" (aka iterate the vertex shader 4 times with no vertex data) but the vertex shader can fetch data from a different location (the ubo) with the help of gl_vertexid to determine the index in the ubo and the vertex position to generate.